code wiki / _hdl_build / nx_connect_serve.nx
nx_connect_serve.nx source
↩ module page · 2082 lines · 116720 B
1// nx_connect_serve.nx -- the CONNECT interactive app, PURE CORE (no main).
2//
3// 2026-07-23 REWRITE (operator: "from the first byte up with a better ui emitter, then wire all the
4// capabilities together"): this was ONE page of hand-rolled inline CSS. It is now a MULTI-PAGE app
5// emitted entirely through nx_connect_ui -- the app-tier UI emitter that carries the canonical
6// nishi_ds design tokens -- with real cross-page navigation, and it surfaces capabilities that were
7// gate-proven but INVISIBLE to any actual user until now.
8//
9// THE SURFACES (each one drives a gated organ, not a mock):
10// / home: what this is, live counters, and a route into every capability
11// /partners reciprocal language exchange -- a one-sided pair renders 0 (the R1 harmonic-mean law)
12// /chat real inline translation through nx_mt_multi, unlimited BY CONSTRUCTION (no quota
13// state exists in this file), corrections attach without destroying the original
14// /reconnect *** the LDS signature, LIVE FOR THE FIRST TIME: member <-> missionary reconnection
15// through the SHARED library nx_connect_lds_reconnect_lib -- the same code the gate
16// verifies. Same mission AND shared area AND time overlap are all NECESSARY, and
17// discovery is consent-gated both ways, so the page can SHOW you why someone does
18// not surface. Nothing is surfaced at all until you opt in.
19// /community ward/branch COMMUNITY group (never congregation administration) + an honest poll
20// where a revote REPLACES, so a tally can never inflate
21// /events real-world meetups with the TWO-DEEP youth wall enforced live and the refusal
22// RENDERED, not hidden
23//
24// Zero JS, render-on-POST, per-session isolated state (see nx_connect_accounts). All user text is escaped
25// on the way out. PURE CORE: cs_handle(ctx, req, reqlen, out, cap).
26// license_tier: ORIGINAL
27import "nx_syscalls.nx"
28import "nx_mt_multi.nx"
29import "nx_connect_ui.nx"
30import "nx_connect_lds_reconnect_lib.nx"
31import "nx_connect_match_lib.nx"
32import "nx_connect_youth_lib.nx"
33import "nx_connect_wards_lib.nx"
34import "nx_connect_lanes_lib.nx"
35import "nx_connect_events_lib.nx"
36import "nx_connect_polls_lib.nx"
37import "nx_connect_group_lib.nx"
38
39const CS_MSGCAP: i64 = 32
40const CS_TXT: i64 = 128
41const CS_SLOTS: i64 = 32 // ctx slots (was 16; grown for the reconnect + community state)
42
43// ctx slots 0..8 are the ORIGINAL layout -- nx_connect_accounts serialises them by index, so they must
44// keep their meaning. New state is strictly APPENDED at 9+ (rule 19: additive, old snapshots still load).
45const CS_NMSG: i64 = 0
46const CS_SPK: i64 = 1
47const CS_TXTA: i64 = 2
48const CS_CORR: i64 = 3
49const CS_EV1_GOING: i64 = 4
50const CS_EV2_ADULTS: i64 = 5
51const CS_EV2_MINORS: i64 = 6
52const CS_BANNER: i64 = 7
53// ctx[8] = poll vote (kept at its historic index for snapshot compatibility)
54const CS_POLL: i64 = 8
55const CS_RC_CONSENT: i64 = 9 // reconnection discovery opt-in (Art.9-style; 0 = surface nothing)
56const CS_RC_MISSION: i64 = 10
57const CS_RC_AREA: i64 = 11
58const CS_RC_FROM: i64 = 12 // year
59const CS_RC_TO: i64 = 13 // year
60const CS_RC_DONE: i64 = 14 // a search has been run in this session
61const CS_WARD: i64 = 15 // joined the ward/branch community group
62const CS_FAITH_CONSENT: i64 = 16 // explicit faith consent (Art.9) -- ward membership is faith-revealing
63const CS_LANE_ME: i64 = 17 // the lane I have opted into for this relationship
64const CS_LANE_THEM: i64 = 18 // the lane they have opted into
65const CS_BLOCKED: i64 = 19 // either side may block, unilaterally
66const CS_CT: i64 = 20 // POINTER to the content-blind ciphertext log (set in world_new, not a data slot)
67const CS_NCT: i64 = 21 // count of stored ciphertexts (data slot; persisted)
68const CS_CTHEX: i64 = 256 // max ciphertext-hex length per sealed message
69
70// ---- SHARED content-blind plane (cross-session): X25519 pubkey directory + routed sealed messages ----
71// The per-ctx log above is one session's private view; REAL two-user E2E needs ciphertext to CROSS
72// sessions. This plane is one bounded arena the daemon owns: a name -> X25519-public-key directory
73// (public keys are public by definition, so storing them keeps the server content-blind) and a routed
74// message store from|to|seq|ct. The server never holds a private key and never sees plaintext.
75// Header slots: sh[0]=npub sh[1]=nmsg sh[2]=last pub idx touched sh[3]=pub generation (upsert counter)
76const SH_PUBCAP: i64 = 64 // bounded + declared: directory capacity
77const SH_MSGCAP: i64 = 128 // bounded + declared: routed ciphertext capacity
78const SH_NAME: i64 = 32 // stored name bytes (z-term inside)
79const SH_PUBHEX: i64 = 68 // 64 hex chars + z
80const SH_SEQ: i64 = 24 // decimal per-message nonce seq (z-term)
81const SH_PUBSTRIDE: i64 = 100 // SH_NAME + SH_PUBHEX
82const SH_MSGSTRIDE: i64 = 344 // from(32) + to(32) + seq(24) + ct(CS_CTHEX)
83const SH_PUB0: i64 = 512 // pub arena byte offset inside the shared block
84const SH_MSG0: i64 = 6912 // SH_PUB0 + SH_PUBCAP*SH_PUBSTRIDE
85const SH_DEL0: i64 = 51072 // SH_MSG0 + SH_MSGCAP*SH_MSGSTRIDE (6912 + 128*344); 1 delivered-flag per msg
86// IDENTITY BINDING (kills trust-on-first-use): a parallel region indexed 1:1 with the pub directory.
87// Each slot = 32-byte authenticated owner user-id (raw) + 1 verified flag. SH_OWN0 = SH_DEL0 + SH_MSGCAP
88// (128 delivered flags) = 51200; region end = 51200 + 64*40 = 53760, inside the 65536 shared block.
89const SH_OWN0: i64 = 51200
90const SH_OWNSTRIDE: i64 = 40
91
92// banner codes
93const CS_B_NONE: i64 = 0
94const CS_B_RSVP_NO: i64 = 1
95const CS_B_RSVP_OK: i64 = 2
96const CS_B_VOTE_OK: i64 = 3
97const CS_B_CONSENT_ON: i64 = 4
98const CS_B_CONSENT_OFF: i64 = 5
99const CS_B_SEARCHED: i64 = 6
100const CS_B_JOINED: i64 = 7
101const CS_B_JOIN_NO: i64 = 8 // join refused: no faith consent
102const CS_B_FAITH_ON: i64 = 9
103const CS_B_FAITH_OFF: i64 = 10
104const CS_B_ALREADY: i64 = 11 // idempotent re-join
105const CS_B_LANE: i64 = 12
106const CS_B_BLOCK: i64 = 13
107const CS_B_UNBLOCK: i64 = 14
108const CS_B_RSVP_FULL: i64 = 15 // engine returned -2: at capacity
109const CS_B_RSVP_ADULTONLY: i64 = 16 // engine returned -4: a minor on a non-youth event
110const CS_B_RSVP_NOTMEMBER: i64 = 17 // engine returned -1: not in the event's group (never silently ignored)
111const CS_B_SEALED: i64 = 18 // a client-encrypted ciphertext was received + stored (server never saw plaintext)
112const CS_B_SEAL_BAD: i64 = 19 // rejected: not valid ciphertext hex
113const CS_B_CONTACT_ADDED: i64 = 25
114const CS_B_CONTACT_DUP: i64 = 26
115const CS_B_CONTACT_BAD: i64 = 27
116const CS_B_CONTACT_SELF: i64 = 28
117const CS_B_CONTACT_FULL: i64 = 29
118const CS_B_CONTACT_RM: i64 = 30
119
120// page codes (drive the aria-current tab state)
121const CS_P_HOME: i64 = 0
122const CS_P_PARTNERS: i64 = 1
123const CS_P_CHAT: i64 = 2
124const CS_P_RECON: i64 = 3
125const CS_P_COMMUNITY: i64 = 4
126const CS_P_EVENTS: i64 = 5
127const CS_P_SECURE: i64 = 6 // the content-blind inbox: ciphertext the server received but cannot read
128const CS_P_LOGIN: i64 = 7 // sign in (OPAQUE aPAKE form)
129const CS_P_REGISTER: i64 = 8 // create an account
130const CS_P_ACCOUNT: i64 = 9 // signed-in identity home (sign out lives here)
131const CS_P_CONTACTS: i64 = 10 // the signed-in contact list
132
133// AUTH ctx slots (24..26). NOT persisted in the .cst snapshot -- they are per-request identity the daemon
134// resolves (nxc_auth cookie -> olg_whoami) and stamps here BEFORE rendering. cs_world_reset clears them, so
135// a gate that never sets them renders the logged-OUT app (the fail-safe default).
136const CS_AUTH: i64 = 24 // 0 = anonymous, 1 = an OPAQUE session validated this request
137const CS_HANDLE: i64 = 25 // pointer to the authenticated handle z-string (0 when anonymous)
138const CS_AUTH_EVENT: i64 = 26 // survives cs_handle2's banner reset -> mapped to a banner just before render
139const CS_MNEMONIC: i64 = 27 // ptr to the 24-word recovery phrase, set ONCE right after register (else 0)
140const CS_CONTACTS: i64 = 28 // ptr to the per-account contact-handle buffer (mmap in cs_world_new)
141const CS_NCONTACTS: i64 = 29 // number of contacts (persisted with the world)
142const CS_SH: i64 = 30 // ptr to the shared plane (daemon-set per request; 0 in offline gates)
143const CS_UID: i64 = 31 // ptr to the authenticated 32-byte user-id (daemon-set; 0 when anonymous)
144const CS_CONTACT_MAX: i64 = 128 // bounded + declared: max contacts per account
145const CS_CONTACT_LEN: i64 = 32 // stored bytes per contact handle (z-term inside)
146// CS_AUTH_EVENT codes
147const CS_AE_NONE: i64 = 0
148const CS_AE_SIGNED_IN: i64 = 1
149const CS_AE_REGISTERED: i64 = 2
150const CS_AE_SIGNED_OUT: i64 = 3
151const CS_AE_LOGIN_FAIL: i64 = 4 // wrong handle OR passphrase -- deliberately indistinguishable
152const CS_AE_REG_TAKEN: i64 = 5 // handle already exists
153const CS_AE_REG_BAD: i64 = 6 // handle/passphrase failed the input rules
154
155// years are stored as a month index so the shared library's integer time-overlap works unchanged.
156const CS_EPOCH_YEAR: i64 = 2010
157
158// ---------------------------------------------------------------------------- small helpers
159
160func cs_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
161func cs_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){dst[off+i]=s[i]; i=i+1} return off+i }
162func cs_catn(dst: *u8, off: i64, v: i64) -> i64 { return cu_putn(dst, off, v) }
163func cs_esc(dst: *u8, off: i64, s: *u8) -> i64 { return cu_esc(dst, off, s) }
164func cs_seq(a: *u8, b: *u8) -> i64 {
165 var i: i64=0
166 while a[i]!=(0 as u8) { if a[i]!=b[i] { return 0 } i=i+1 }
167 if b[i]!=(0 as u8) { return 0 }
168 return 1
169}
170func cs_starts(s: *u8, pfx: *u8) -> i64 { var i: i64=0; while pfx[i]!=(0 as u8){ if s[i]!=pfx[i] { return 0 } i=i+1 } return 1 }
171func cs_hexval(c: i64) -> i64 {
172 if c>=48 { if c<=57 { return c-48 } }
173 if c>=65 { if c<=70 { return c-55 } }
174 if c>=97 { if c<=102 { return c-87 } }
175 return 0
176}
177// parse a small non-negative integer out of a NUL-terminated string (fail -> def)
178func cs_atoi(s: *u8, def: i64) -> i64 {
179 if s[0]==(0 as u8) { return def }
180 var v: i64=0
181 var i: i64=0
182 var ok: i64=0
183 while s[i]!=(0 as u8) {
184 let c: i64 = s[i] as i64
185 if c>=48 { if c<=57 { v=v*10+(c-48); ok=1 } }
186 i=i+1
187 }
188 if ok==0 { return def }
189 return v
190}
191func cs_year_to_month(y: i64) -> i64 { return (y - CS_EPOCH_YEAR) * 12 }
192
193// urldecoded form value (the proven of_form_get idiom)
194func cs_form_get(body: *u8, blen: i64, key: *u8, out: *u8, cap: i64) -> i64 {
195 let kl: i64 = cs_slen(key)
196 var i: i64=0
197 while i<blen {
198 var atk: i64=0
199 if i==0 { atk=1 } else { if body[i-1]==(38 as u8) { atk=1 } }
200 if atk==1 {
201 var m: i64=1
202 var j: i64=0
203 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 } } }
204 if m==1 { if i+kl<blen { if body[i+kl]==(61 as u8) {
205 var q: i64=i+kl+1
206 var t: i64=0
207 var go: i64=1
208 while go==1 {
209 if q>=blen { go=0 } else {
210 let c: i64 = body[q] as i64
211 if c==38 { go=0 } else {
212 var ch: i64=c
213 if c==43 { ch=32 }
214 if c==37 { if q+2<blen { ch=cs_hexval(body[q+1] as i64)*16+cs_hexval(body[q+2] as i64); q=q+2 } }
215 if t<cap-1 { out[t]=ch as u8; t=t+1 }
216 q=q+1
217 }
218 }
219 }
220 out[t]=0 as u8
221 return t
222 } } }
223 }
224 i=i+1
225 }
226 out[0]=0 as u8
227 return 0
228}
229
230func cs_write_all(fd: i64, buf: *u8, n: i64) -> i64 {
231 var w: i64=0
232 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 }
233 return 0
234}
235// read ONE full HTTP request: headers until CRLFCRLF plus Content-Length body (the proven idiom)
236func cs_read_req(fd: i64, buf: *u8, cap: i64) -> i64 {
237 var n: i64=0
238 var hdr_end: i64=0-1
239 var want: i64=0-1
240 var go: i64=1
241 while go==1 {
242 if n>=cap-1 { go=0 } else {
243 let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap-1-n)
244 if r<=0 { go=0 } else {
245 n=n+r
246 if hdr_end<0 {
247 var i: i64=0
248 while i+3<n {
249 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 } } } }
250 i=i+1
251 }
252 if hdr_end>=0 {
253 var cl: i64=0-1
254 let key: *u8 = "Content-Length:" as *u8
255 let kl: i64=15
256 var j: i64=0
257 while j+kl<hdr_end {
258 var m: i64=1
259 var q: i64=0
260 while q<kl { if buf[j+q]!=key[q] { m=0; q=kl } else { q=q+1 } }
261 if m==1 {
262 var v: i64=0
263 var t: i64=j+kl
264 while t<hdr_end {
265 let c: i64 = buf[t] as i64
266 if c>=48 { if c<=57 { v=v*10+(c-48) } }
267 if c==13 { t=hdr_end }
268 t=t+1
269 }
270 cl=v
271 j=hdr_end
272 }
273 j=j+1
274 }
275 if cl<0 { want=hdr_end } else { want=hdr_end+cl }
276 }
277 }
278 if want>=0 { if n>=want { go=0 } }
279 }
280 }
281 }
282 return n
283}
284
285// ---------------------------------------------------------------------------- demo world
286
287func cs_world_reset(ctx: *i64) -> i64 {
288 let spk: *i64 = ctx[CS_SPK] as *i64
289 let txt: *u8 = ctx[CS_TXTA] as *u8
290 let corr: *u8 = ctx[CS_CORR] as *u8
291 spk[0]=1
292 var o: i64 = cs_cat(txt, 0*CS_TXT, "привет друг" as *u8)
293 txt[o]=0 as u8
294 corr[0*CS_TXT]=0 as u8
295 spk[1]=0
296 o = cs_cat(txt, 1*CS_TXT, "hello friend" as *u8)
297 txt[o]=0 as u8
298 corr[1*CS_TXT]=0 as u8
299 ctx[CS_NMSG]=2
300 ctx[CS_EV1_GOING]=2
301 ctx[CS_EV2_ADULTS]=1
302 ctx[CS_EV2_MINORS]=0
303 ctx[CS_BANNER]=CS_B_NONE
304 ctx[CS_POLL]=0
305 ctx[CS_RC_CONSENT]=0
306 ctx[CS_RC_MISSION]=7
307 ctx[CS_RC_AREA]=2
308 ctx[CS_RC_FROM]=2019
309 ctx[CS_RC_TO]=2021
310 ctx[CS_RC_DONE]=0
311 ctx[CS_WARD]=0
312 ctx[CS_FAITH_CONSENT]=0
313 ctx[CS_LANE_ME]=LN_LANE_LANG
314 ctx[CS_LANE_THEM]=LN_LANE_LANG
315 ctx[CS_BLOCKED]=0
316 ctx[CS_NCT]=0
317 // AUTH is per-request identity, never part of the persisted world -- default to logged-OUT (fail-safe).
318 ctx[CS_AUTH]=0
319 ctx[CS_HANDLE]=0
320 ctx[CS_AUTH_EVENT]=CS_AE_NONE
321 ctx[CS_MNEMONIC]=0
322 ctx[CS_NCONTACTS]=0
323 ctx[CS_SH]=0
324 return 0
325}
326func cs_world_new() -> *i64 {
327 let ctx: *i64 = sys_mmap(CS_SLOTS*8) as *i64
328 let spk: *i64 = sys_mmap(CS_MSGCAP*8) as *i64
329 let txt: *u8 = sys_mmap(CS_MSGCAP*CS_TXT)
330 let corr: *u8 = sys_mmap(CS_MSGCAP*CS_TXT)
331 let ctlog: *u8 = sys_mmap(CS_MSGCAP*CS_CTHEX) // content-blind ciphertext log (server never decrypts it)
332 let contacts: *u8 = sys_mmap(CS_CONTACT_MAX*CS_CONTACT_LEN) // per-account contact handles
333 ctx[CS_SPK]=spk as i64
334 ctx[CS_TXTA]=txt as i64
335 ctx[CS_CORR]=corr as i64
336 ctx[CS_CT]=ctlog as i64
337 ctx[CS_CONTACTS]=contacts as i64
338 cs_world_reset(ctx)
339 return ctx
340}
341
342// ---- contact list ops (pure; per-account, stored in the world, persisted via nx_connect_accounts) ----
343func cs_contact_at(ctx: *i64, i: i64) -> *u8 { return (ctx[CS_CONTACTS] + i*CS_CONTACT_LEN) as *u8 }
344func cs_contact_find(ctx: *i64, name: *u8) -> i64 {
345 var i: i64=0
346 while i<ctx[CS_NCONTACTS] { if cs_seq(cs_contact_at(ctx,i), name)==1 { return i } i=i+1 }
347 return 0-1
348}
349// add a contact handle: 1 added, 0 invalid handle, -1 already present, -2 full, -3 = it's yourself.
350func cs_contact_add(ctx: *i64, name: *u8) -> i64 {
351 if cs_name_ok(name)==0 { return 0 }
352 if ctx[CS_HANDLE]!=0 { if cs_seq(name, ctx[CS_HANDLE] as *u8)==1 { return 0-3 } }
353 if cs_contact_find(ctx, name)>=0 { return 0-1 }
354 if ctx[CS_NCONTACTS]>=CS_CONTACT_MAX { return 0-2 }
355 let row: *u8 = cs_contact_at(ctx, ctx[CS_NCONTACTS])
356 var w: i64=0
357 while name[w]!=(0 as u8) { if w<CS_CONTACT_LEN-1 { row[w]=name[w] } w=w+1 }
358 if w>CS_CONTACT_LEN-1 { w=CS_CONTACT_LEN-1 }
359 row[w]=0 as u8
360 ctx[CS_NCONTACTS]=ctx[CS_NCONTACTS]+1
361 return 1
362}
363// remove a contact by handle: 1 removed (compacted), 0 not found.
364func cs_contact_remove(ctx: *i64, name: *u8) -> i64 {
365 let f: i64 = cs_contact_find(ctx, name)
366 if f<0 { return 0 }
367 var i: i64=f
368 while i<ctx[CS_NCONTACTS]-1 {
369 let dst: *u8 = cs_contact_at(ctx, i)
370 let src: *u8 = cs_contact_at(ctx, i+1)
371 var k: i64=0
372 while k<CS_CONTACT_LEN { dst[k]=src[k]; k=k+1 }
373 i=i+1
374 }
375 ctx[CS_NCONTACTS]=ctx[CS_NCONTACTS]-1
376 return 1
377}
378
379// ---------------------------------------------------------------------------- shared plane (pure ops)
380
381func cs_shared_new() -> *i64 {
382 let sh: *i64 = sys_mmap(65536) as *i64
383 sh[0]=0 // npub
384 sh[1]=0 // nmsg
385 sh[2]=0-1 // last pub idx touched
386 sh[3]=0 // pub generation (upsert counter)
387 sh[4]=0 // ack generation (delete-on-delivery counter)
388 sh[5]=0-1 // last-acked msg idx
389 return sh
390}
391func sh_pub_at(sh: *i64, i: i64) -> *u8 { return (sh as i64 + SH_PUB0 + i*SH_PUBSTRIDE) as *u8 }
392func sh_msg_at(sh: *i64, i: i64) -> *u8 { return (sh as i64 + SH_MSG0 + i*SH_MSGSTRIDE) as *u8 }
393func sh_del_at(sh: *i64, i: i64) -> *u8 { return (sh as i64 + SH_DEL0 + i) as *u8 } // 1 = delivered/tombstoned
394// owner-binding slot for pub row i: bytes [0..31]=authenticated user-id, byte[32]=verified flag (0/1)
395func sh_own_at(sh: *i64, i: i64) -> *u8 { return (sh as i64 + SH_OWN0 + i*SH_OWNSTRIDE) as *u8 }
396// raw 32-byte copy / compare (the OPAQUE user-id is a 32-byte hash)
397func cs_copy32(dst: *u8, src: *u8) -> i64 { var i: i64=0; while i<32 { dst[i]=src[i]; i=i+1 } return 32 }
398func cs_eq32(a: *u8, b: *u8) -> i64 { var i: i64=0; while i<32 { if a[i]!=b[i] { return 0 } i=i+1 } return 1 }
399// emit nbytes of raw bytes as lowercase hex into dst[off..]; returns the new offset
400func cs_hex_emit(dst: *u8, off: i64, raw: *u8, nbytes: i64) -> i64 {
401 let digits: *u8 = "0123456789abcdef" as *u8
402 var i: i64=0
403 var o: i64=off
404 while i<nbytes {
405 let b: i64 = (raw[i] as i64) & 0xff
406 dst[o] = digits[(b>>4) & 0xf]; o=o+1
407 dst[o] = digits[b & 0xf]; o=o+1
408 i=i+1
409 }
410 return o
411}
412// decode a hex z-string into out (up to 32 bytes); returns bytes decoded (0 on empty/odd length)
413func cs_hex_to_raw32(hex: *u8, out: *u8) -> i64 {
414 var n: i64=0
415 while hex[n]!=(0 as u8) { n=n+1 }
416 if n==0 { return 0 }
417 if (n/2)*2!=n { return 0 }
418 var half: i64 = n/2
419 if half>32 { half=32 }
420 var i: i64=0
421 while i<half {
422 let hi: i64 = cs_hexval(hex[i*2] as i64)
423 let lo: i64 = cs_hexval(hex[i*2+1] as i64)
424 out[i] = ((hi<<4)|lo) as u8
425 i=i+1
426 }
427 return half
428}
429
430// lowercase ASCII A-Z in place on a z-terminated string (handles arrive from forms possibly capitalised).
431func cs_lower_ascii(s: *u8) -> i64 {
432 var i: i64=0
433 while s[i]!=(0 as u8) { let c: i64 = s[i] as i64 & 0xff; if c>=65 { if c<=90 { s[i]=(c+32) as u8 } } i=i+1 }
434 return 0
435}
436// handle grammar [a-z0-9_]{1,24} -- lowercase-only keeps the directory canonical (the client lowercases)
437func cs_name_ok(s: *u8) -> i64 {
438 var i: i64=0
439 while s[i]!=(0 as u8) {
440 let c: i64 = s[i] as i64
441 var ok: i64=0
442 if c>=97 { if c<=122 { ok=1 } }
443 if c>=48 { if c<=57 { ok=1 } }
444 if c==95 { ok=1 }
445 if ok==0 { return 0 }
446 i=i+1
447 }
448 if i<1 { return 0 }
449 if i>24 { return 0 }
450 return 1
451}
452// length of s if EVERY char is a hex digit, else -1
453func cs_hex_len(s: *u8) -> i64 {
454 var i: i64=0
455 while s[i]!=(0 as u8) {
456 let c: i64 = s[i] as i64
457 var ok: i64=0
458 if c>=48 { if c<=57 { ok=1 } }
459 if c>=97 { if c<=102 { ok=1 } }
460 if c>=65 { if c<=70 { ok=1 } }
461 if ok==0 { return 0-1 }
462 i=i+1
463 }
464 return i
465}
466func cs_digits_ok(s: *u8) -> i64 {
467 var i: i64=0
468 while s[i]!=(0 as u8) {
469 let c: i64 = s[i] as i64
470 if c<48 { return 0 }
471 if c>57 { return 0 }
472 i=i+1
473 }
474 if i<1 { return 0 }
475 if i>20 { return 0 }
476 return 1
477}
478func cs_sh_copyz(dst: *u8, src: *u8, cap: i64) -> i64 {
479 var i: i64=0
480 var w: i64=0
481 while src[i]!=(0 as u8) {
482 if w<cap-1 { dst[w]=src[i]; w=w+1 }
483 i=i+1
484 }
485 dst[w]=0 as u8
486 return w
487}
488func cs_sh_pub_find(sh: *i64, name: *u8) -> i64 {
489 var i: i64=0
490 while i<sh[0] {
491 if cs_seq(sh_pub_at(sh,i), name)==1 { return i }
492 i=i+1
493 }
494 return 0-1
495}
496// upsert into the directory; 1 = stored new, 2 = key replaced (latest wins), 0 invalid, -1 full
497func cs_sh_pub_put(sh: *i64, name: *u8, pub: *u8) -> i64 {
498 if cs_name_ok(name)==0 { return 0 }
499 if cs_hex_len(pub)!=64 { return 0 }
500 let f: i64 = cs_sh_pub_find(sh, name)
501 if f>=0 {
502 let row: *u8 = sh_pub_at(sh, f)
503 cs_sh_copyz((row as i64 + SH_NAME) as *u8, pub, SH_PUBHEX)
504 sh[2]=f
505 sh[3]=sh[3]+1
506 return 2
507 }
508 if sh[0]>=SH_PUBCAP { return 0-1 }
509 let row2: *u8 = sh_pub_at(sh, sh[0])
510 cs_sh_copyz(row2, name, SH_NAME)
511 cs_sh_copyz((row2 as i64 + SH_NAME) as *u8, pub, SH_PUBHEX)
512 sh[2]=sh[0]
513 sh[3]=sh[3]+1
514 sh[0]=sh[0]+1
515 return 1
516}
517// AUTHENTICATED upsert -- binds the key to a 32-byte owner identity and REFUSES to let anyone but that
518// owner change a VERIFIED handle's key. This is what kills trust-on-first-use: once alice's account has
519// published her key, no other account (and no anonymous caller) can substitute a different key under the
520// name "alice", so a relay operator or impostor cannot man-in-the-middle the directory. is_auth=1 means
521// `owner` points at a validated 32-byte user-id; is_auth=0 is a legacy anonymous publish (stored
522// UNVERIFIED, and never permitted to overwrite a verified handle). 1 new, 2 replaced, 0 invalid, -1 full,
523// -4 REFUSED (a non-owner tried to rebind a verified handle).
524func cs_sh_pub_put2(sh: *i64, name: *u8, pub: *u8, owner: *u8, is_auth: i64) -> i64 {
525 if cs_name_ok(name)==0 { return 0 }
526 if cs_hex_len(pub)!=64 { return 0 }
527 let f: i64 = cs_sh_pub_find(sh, name)
528 if f>=0 {
529 let ow: *u8 = sh_own_at(sh, f)
530 let wasver: i64 = ow[32] as i64
531 if wasver==1 {
532 if is_auth==0 { return 0-4 }
533 if cs_eq32(ow, owner)==0 { return 0-4 }
534 }
535 let row: *u8 = sh_pub_at(sh, f)
536 cs_sh_copyz((row as i64 + SH_NAME) as *u8, pub, SH_PUBHEX)
537 if is_auth==1 { cs_copy32(ow, owner); ow[32]=1 as u8 }
538 sh[2]=f
539 sh[3]=sh[3]+1
540 return 2
541 }
542 if sh[0]>=SH_PUBCAP { return 0-1 }
543 let row2: *u8 = sh_pub_at(sh, sh[0])
544 cs_sh_copyz(row2, name, SH_NAME)
545 cs_sh_copyz((row2 as i64 + SH_NAME) as *u8, pub, SH_PUBHEX)
546 let ow2: *u8 = sh_own_at(sh, sh[0])
547 if is_auth==1 { cs_copy32(ow2, owner); ow2[32]=1 as u8 } else { ow2[32]=0 as u8 }
548 sh[2]=sh[0]
549 sh[3]=sh[3]+1
550 sh[0]=sh[0]+1
551 return 1
552}
553// routed ciphertext; idx >=0 stored, -1 full, -2 invalid, -3 unknown recipient (a message nobody
554// could ever decrypt is refused LOUDLY, never stored silently)
555func cs_sh_msg_add(sh: *i64, from: *u8, to: *u8, seq: *u8, ct: *u8) -> i64 {
556 if cs_name_ok(from)==0 { return 0-2 }
557 if cs_name_ok(to)==0 { return 0-2 }
558 if cs_digits_ok(seq)==0 { return 0-2 }
559 let hn: i64 = cs_hex_len(ct)
560 if hn<2 { return 0-2 }
561 if (hn/2)*2!=hn { return 0-2 }
562 if hn>CS_CTHEX-1 { return 0-2 }
563 if cs_sh_pub_find(sh, to)<0 { return 0-3 }
564 if sh[1]>=SH_MSGCAP { return 0-1 }
565 let row: *u8 = sh_msg_at(sh, sh[1])
566 cs_sh_copyz(row, from, SH_NAME)
567 cs_sh_copyz((row as i64 + SH_NAME) as *u8, to, SH_NAME)
568 cs_sh_copyz((row as i64 + SH_NAME + SH_NAME) as *u8, seq, SH_SEQ)
569 cs_sh_copyz((row as i64 + SH_NAME + SH_NAME + SH_SEQ) as *u8, ct, CS_CTHEX)
570 let df: *u8 = sh_del_at(sh, sh[1]); df[0]=0 as u8 // a freshly stored message is not-yet-delivered
571 sh[1]=sh[1]+1
572 return sh[1]-1
573}
574// find a routed message by (from,to,seq) that is NOT already tombstoned; -1 if none.
575func cs_sh_msg_find(sh: *i64, from: *u8, to: *u8, seq: *u8) -> i64 {
576 var i: i64=0
577 while i<sh[1] {
578 let dfp: *u8 = sh_del_at(sh, i)
579 if (dfp[0] as i64)==0 {
580 let row: *u8 = sh_msg_at(sh, i)
581 if cs_seq(row, from)==1 { if cs_seq((row as i64 + SH_NAME) as *u8, to)==1 { if cs_seq((row as i64 + SH_NAME + SH_NAME) as *u8, seq)==1 { return i } } }
582 }
583 i=i+1
584 }
585 return 0-1
586}
587// DELETE-ON-DELIVERY: the recipient's device ACKs it holds a message in its own vault -> the server
588// TOMBSTONES it (delivered=1) so it is never served again. The device vault is now the durable copy;
589// the server retains nothing servable. Returns 1 tombstoned, 0 not-found (idempotent: a 2nd ack = 0).
590func cs_sh_msg_ack(sh: *i64, from: *u8, to: *u8, seq: *u8) -> i64 {
591 if cs_name_ok(from)==0 { return 0 }
592 if cs_name_ok(to)==0 { return 0 }
593 if cs_digits_ok(seq)==0 { return 0 }
594 let idx: i64 = cs_sh_msg_find(sh, from, to, seq)
595 if idx<0 { return 0 }
596 let dfa: *u8 = sh_del_at(sh, idx); dfa[0]=1 as u8
597 sh[5]=idx
598 sh[4]=sh[4]+1
599 return 1
600}
601// append-log row emitters (TAB-separated, one line per row) -- the daemon persists ONLY deltas with
602// these, so the log stays append-only (additive law), and cs_sh_replay reconstructs state at boot.
603func cs_sh_emit_pub(sh: *i64, i: i64, dst: *u8, off: i64) -> i64 {
604 let row: *u8 = sh_pub_at(sh, i)
605 let ow: *u8 = sh_own_at(sh, i)
606 var o: i64 = cs_cat(dst, off, "P\t" as *u8)
607 o = cs_cat(dst, o, row)
608 o = cs_cat(dst, o, "\t" as *u8)
609 o = cs_cat(dst, o, (row as i64 + SH_NAME) as *u8)
610 o = cs_cat(dst, o, "\t" as *u8) // NEW fields (older logs omit them and replay as unverified)
611 o = cs_hex_emit(dst, o, ow, 32) // owner user-id (64 hex chars; all-zero when anonymous)
612 o = cs_cat(dst, o, "\t" as *u8)
613 o = cs_catn(dst, o, ow[32] as i64) // verified flag 0/1
614 o = cs_cat(dst, o, "\n" as *u8)
615 return o
616}
617func cs_sh_emit_msg(sh: *i64, i: i64, dst: *u8, off: i64) -> i64 {
618 let row: *u8 = sh_msg_at(sh, i)
619 var o: i64 = cs_cat(dst, off, "M\t" as *u8)
620 o = cs_cat(dst, o, row)
621 o = cs_cat(dst, o, "\t" as *u8)
622 o = cs_cat(dst, o, (row as i64 + SH_NAME) as *u8)
623 o = cs_cat(dst, o, "\t" as *u8)
624 o = cs_cat(dst, o, (row as i64 + SH_NAME + SH_NAME) as *u8)
625 o = cs_cat(dst, o, "\t" as *u8)
626 o = cs_cat(dst, o, (row as i64 + SH_NAME + SH_NAME + SH_SEQ) as *u8)
627 o = cs_cat(dst, o, "\n" as *u8)
628 return o
629}
630// tombstone delta row: "D<TAB>from<TAB>to<TAB>seq" -- replayed as a delivered-mark on the matching message.
631func cs_sh_emit_tomb(sh: *i64, i: i64, dst: *u8, off: i64) -> i64 {
632 let row: *u8 = sh_msg_at(sh, i)
633 var o: i64 = cs_cat(dst, off, "D\t" as *u8)
634 o = cs_cat(dst, o, row)
635 o = cs_cat(dst, o, "\t" as *u8)
636 o = cs_cat(dst, o, (row as i64 + SH_NAME) as *u8)
637 o = cs_cat(dst, o, "\t" as *u8)
638 o = cs_cat(dst, o, (row as i64 + SH_NAME + SH_NAME) as *u8)
639 o = cs_cat(dst, o, "\n" as *u8)
640 return o
641}
642// COMPACTION: emit a MINIMAL append-only log that reproduces the current LIVE state -- all pubkeys (P rows)
643// plus M rows ONLY for messages that are NOT tombstoned. A delivered (tombstoned) message contributes
644// NOTHING: its ciphertext + from|to|seq bytes are physically DROPPED, not just logically hidden. The daemon
645// atomically rewrites connect/shared.cbl with this, so delivered content is GONE FROM DISK -- delete-on-
646// delivery becomes a physical guarantee, and the log can never grow unbounded. Returns byte length.
647func cs_sh_compact_emit(sh: *i64, dst: *u8) -> i64 {
648 var o: i64 = 0
649 var pi: i64=0
650 while pi<sh[0] { o = cs_sh_emit_pub(sh, pi, dst, o); pi=pi+1 }
651 var mi: i64=0
652 while mi<sh[1] {
653 let dfc: *u8 = sh_del_at(sh, mi)
654 if (dfc[0] as i64)==0 { o = cs_sh_emit_msg(sh, mi, dst, o) }
655 mi=mi+1
656 }
657 return o
658}
659// count tombstoned (delivered) messages currently held -- the daemon compacts when this crosses a threshold.
660func cs_sh_tomb_count(sh: *i64) -> i64 {
661 var c: i64=0
662 var i: i64=0
663 while i<sh[1] { let dft: *u8 = sh_del_at(sh, i); if (dft[0] as i64)==1 { c=c+1 } i=i+1 }
664 return c
665}
666// copy the next TAB/NL-delimited field from buf[i..] into out; returns the index AFTER a TAB delimiter
667func sh_field(buf: *u8, i: i64, n: i64, out: *u8, cap: i64) -> i64 {
668 var p: i64=i
669 var t: i64=0
670 var go: i64=1
671 while go==1 {
672 if p>=n { go=0 } else {
673 let c: i64 = buf[p] as i64
674 if c==9 { go=0 } else { if c==10 { go=0 } else {
675 if t<cap-1 { out[t]=c as u8; t=t+1 }
676 p=p+1
677 } }
678 }
679 }
680 out[t]=0 as u8
681 if p<n { if (buf[p] as i64)==9 { p=p+1 } }
682 return p
683}
684// replay a whole append-only log into sh. Later P rows for a name WIN (upsert) -- replay order IS the
685// update order. M rows re-validate through cs_sh_msg_add (bounded caps hold on replay too).
686func cs_sh_replay(sh: *i64, buf: *u8, n: i64) -> i64 {
687 let fa: *u8 = sys_mmap(64)
688 let fb: *u8 = sys_mmap(64)
689 let fs: *u8 = sys_mmap(64)
690 let fp: *u8 = sys_mmap(128)
691 let fc: *u8 = sys_mmap(CS_CTHEX+8)
692 let fo: *u8 = sys_mmap(128) // owner hex field (empty in legacy P rows)
693 let fv: *u8 = sys_mmap(16) // verified flag field (empty in legacy P rows)
694 let ob: *u8 = sys_mmap(64) // decoded 32-byte owner user-id
695 var i: i64=0
696 var rows: i64=0
697 while i<n {
698 let k: i64 = buf[i] as i64
699 if k==80 {
700 var p: i64 = i+1
701 if p<n { if (buf[p] as i64)==9 { p=p+1 } }
702 p = sh_field(buf, p, n, fa, 64) // name
703 p = sh_field(buf, p, n, fp, 128) // pub
704 p = sh_field(buf, p, n, fo, 128) // owner hex (empty in legacy logs)
705 p = sh_field(buf, p, n, fv, 16) // verified flag (empty in legacy logs)
706 if fo[0]==(0 as u8) {
707 cs_sh_pub_put(sh, fa, fp) // legacy row -> unverified
708 } else {
709 if fv[0]==(49 as u8) { // '1' -> verified: rebind the owner identity
710 cs_hex_to_raw32(fo, ob)
711 cs_sh_pub_put2(sh, fa, fp, ob, 1)
712 } else {
713 cs_sh_pub_put(sh, fa, fp) // owner present but unverified
714 }
715 }
716 rows=rows+1
717 }
718 if k==77 {
719 var p2: i64 = i+1
720 if p2<n { if (buf[p2] as i64)==9 { p2=p2+1 } }
721 p2 = sh_field(buf, p2, n, fa, 64)
722 p2 = sh_field(buf, p2, n, fb, 64)
723 p2 = sh_field(buf, p2, n, fs, 64)
724 p2 = sh_field(buf, p2, n, fc, CS_CTHEX+8)
725 cs_sh_msg_add(sh, fa, fb, fs, fc)
726 rows=rows+1
727 }
728 if k==68 { // 'D' tombstone: re-apply the delivered-mark so a delivered message never resurfaces
729 var p3: i64 = i+1
730 if p3<n { if (buf[p3] as i64)==9 { p3=p3+1 } }
731 p3 = sh_field(buf, p3, n, fa, 64)
732 p3 = sh_field(buf, p3, n, fb, 64)
733 p3 = sh_field(buf, p3, n, fs, 64)
734 cs_sh_msg_ack(sh, fa, fb, fs)
735 rows=rows+1
736 }
737 var e: i64=i
738 var go2: i64=1
739 while go2==1 {
740 if e>=n { go2=0 } else {
741 if (buf[e] as i64)==10 { go2=0 } else { e=e+1 }
742 }
743 }
744 i=e+1
745 }
746 return rows
747}
748// signed decimal (cu_putn renders magnitudes only -- negative verdict codes need the sign emitted here)
749func cs_catsn(dst: *u8, off: i64, v: i64) -> i64 {
750 if v<0 {
751 let o: i64 = cs_cat(dst, off, "-" as *u8)
752 return cs_catn(dst, o, 0-v)
753 }
754 return cs_catn(dst, off, v)
755}
756// raw JSON response (the shared-plane routes speak JSON to the wasm client, not the HTML shell)
757func cs_json_resp(body: *u8, bn: i64, out: *u8, cap: i64) -> i64 {
758 var o: i64 = cs_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nCache-Control: no-store\r\nContent-Length: " as *u8)
759 o = cs_catn(out, o, bn)
760 o = cs_cat(out, o, "\r\nConnection: close\r\n\r\n" as *u8)
761 var i: i64=0
762 while i<bn { if o+i<cap { out[o+i]=body[i] } i=i+1 }
763 return o+bn
764}
765
766// ---------------------------------------------------------------------------- shell
767
768func cs_shell_open(ctx: *i64, bp: *u8, dst: *u8, off: i64, page: i64, title: *u8) -> i64 {
769 var o: i64 = cu_head(dst, off, title)
770 o = cu_bar_open(dst, o, "Connect" as *u8, bp)
771 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
772 if page==CS_P_HOME { o = cu_puts(dst, o, "\" aria-current=\"page\">Home</a>" as *u8) } else { o = cu_puts(dst, o, "\">Home</a>" as *u8) }
773 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
774 if page==CS_P_PARTNERS { o = cu_puts(dst, o, "/partners\" aria-current=\"page\">Partners</a>" as *u8) } else { o = cu_puts(dst, o, "/partners\">Partners</a>" as *u8) }
775 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
776 if page==CS_P_CHAT { o = cu_puts(dst, o, "/chat\" aria-current=\"page\">Chat</a>" as *u8) } else { o = cu_puts(dst, o, "/chat\">Chat</a>" as *u8) }
777 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
778 if page==CS_P_RECON { o = cu_puts(dst, o, "/reconnect\" aria-current=\"page\">Reconnect</a>" as *u8) } else { o = cu_puts(dst, o, "/reconnect\">Reconnect</a>" as *u8) }
779 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
780 if page==CS_P_COMMUNITY { o = cu_puts(dst, o, "/community\" aria-current=\"page\">Community</a>" as *u8) } else { o = cu_puts(dst, o, "/community\">Community</a>" as *u8) }
781 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
782 if page==CS_P_EVENTS { o = cu_puts(dst, o, "/events\" aria-current=\"page\">Events</a>" as *u8) } else { o = cu_puts(dst, o, "/events\">Events</a>" as *u8) }
783 // identity area: signed-in handle -> Contacts + Account tabs; signed-out -> Sign in / Register.
784 if ctx[CS_AUTH]==1 {
785 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
786 if page==CS_P_CONTACTS { o = cu_puts(dst, o, "/contacts\" aria-current=\"page\">Contacts</a>" as *u8) } else { o = cu_puts(dst, o, "/contacts\">Contacts</a>" as *u8) }
787 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
788 if page==CS_P_ACCOUNT { o = cu_puts(dst, o, "/account\" aria-current=\"page\">@" as *u8) } else { o = cu_puts(dst, o, "/account\">@" as *u8) }
789 if ctx[CS_HANDLE]!=0 { o = cu_esc(dst, o, ctx[CS_HANDLE] as *u8) }
790 o = cu_puts(dst, o, "</a>" as *u8)
791 } else {
792 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
793 if page==CS_P_LOGIN { o = cu_puts(dst, o, "/login\" aria-current=\"page\">Sign in</a>" as *u8) } else { o = cu_puts(dst, o, "/login\">Sign in</a>" as *u8) }
794 o = cu_puts(dst, o, "<a href=\"" as *u8); o = cu_puts(dst, o, bp)
795 if page==CS_P_REGISTER { o = cu_puts(dst, o, "/register\" aria-current=\"page\">Register</a>" as *u8) } else { o = cu_puts(dst, o, "/register\">Register</a>" as *u8) }
796 }
797 o = cu_bar_close(dst, o)
798 return o
799}
800
801func cs_banner(ctx: *i64, dst: *u8, off: i64) -> i64 {
802 var o: i64 = off
803 let b: i64 = ctx[CS_BANNER]
804 if b==CS_B_RSVP_NO { o = cu_banner(dst, o, 2, "RSVP refused — two-deep rule" as *u8, "A youth may join a youth event only when two adults are already on the roster. Add a second adult leader first. This is enforced by the software, not by a policy page." as *u8) }
805 if b==CS_B_RSVP_OK { o = cu_banner(dst, o, 1, "RSVP recorded" as *u8, "You are on the roster." as *u8) }
806 if b==CS_B_VOTE_OK { o = cu_banner(dst, o, 1, "Vote recorded" as *u8, "A revote replaces your previous ballot — one voter, one ballot, so a tally can never inflate." as *u8) }
807 if b==CS_B_CONSENT_ON { o = cu_banner(dst, o, 1, "Reconnection discovery is on" as *u8, "You can now be found by people you actually overlapped with. You can turn this off at any time and you will disappear from their results immediately." as *u8) }
808 if b==CS_B_CONSENT_OFF { o = cu_banner(dst, o, 0, "Reconnection discovery is off" as *u8, "Nothing about your service or residence is surfaced to anyone." as *u8) }
809 if b==CS_B_SEARCHED { o = cu_banner(dst, o, 1, "Search updated" as *u8, "Results below are recomputed from your mission, area, and years." as *u8) }
810 if b==CS_B_JOINED { o = cu_banner(dst, o, 1, "Joined the community group" as *u8, "You are on the roster. Only people who joined appear there — nobody is listed by default." as *u8) }
811 if b==CS_B_JOIN_NO { o = cu_banner(dst, o, 2, "Join refused — no faith consent on file" as *u8, "Joining a ward or branch group reveals your religious belief, which is special-category data. The join is refused outright rather than recorded and hidden, so there is nothing to leak." as *u8) }
812 if b==CS_B_FAITH_ON { o = cu_banner(dst, o, 1, "Faith consent recorded" as *u8, "You can now join faith community groups. Withdrawing consent removes you from them." as *u8) }
813 if b==CS_B_FAITH_OFF { o = cu_banner(dst, o, 0, "Faith consent withdrawn" as *u8, "You have been removed from the community group, and your belief is no longer recorded anywhere." as *u8) }
814 if b==CS_B_ALREADY { o = cu_banner(dst, o, 0, "Already a member" as *u8, "Joining twice changes nothing — the roster is idempotent, so a repeated tap can never double-count you." as *u8) }
815 if b==CS_B_RSVP_NOTMEMBER { o = cu_banner(dst, o, 2, "Not a member of this group" as *u8, "Events belong to a community group, and only its members can RSVP. Join the group first." as *u8) }
816 if b==CS_B_RSVP_FULL { o = cu_banner(dst, o, 2, "Event is full" as *u8, "Every seat is taken. Capacity is checked AFTER the safety rules, never before — a nearly-full event can never become the reason a youth-safety check gets skipped." as *u8) }
817 if b==CS_B_RSVP_ADULTONLY { o = cu_banner(dst, o, 2, "RSVP refused — not a youth event" as *u8, "The park picnic is not set up for youth, so a young person cannot be added to it at all. That is a property of the event, not a judgement about the person." as *u8) }
818 if b==CS_B_LANE { o = cu_banner(dst, o, 0, "Intent updated" as *u8, "What you are open to has been recorded. The relationship only moves when the other person has opted into the same level." as *u8) }
819 if b==CS_B_BLOCK { o = cu_banner(dst, o, 2, "Blocked" as *u8, "The relationship is severed immediately. Blocking never needed their agreement — only escalation does." as *u8) }
820 if b==CS_B_UNBLOCK { o = cu_banner(dst, o, 1, "Unblocked" as *u8, "The relationship returns to whatever level you BOTH had opted into — not to where it was heading." as *u8) }
821 if b==CS_B_SEALED { o = cu_banner(dst, o, 1, "Ciphertext received" as *u8, "The server stored your message as ciphertext and never saw the words — it holds no key and cannot decrypt it." as *u8) }
822 if b==CS_B_SEAL_BAD { o = cu_banner(dst, o, 2, "Rejected — not valid ciphertext" as *u8, "The sealed inbox accepts only hex ciphertext from the browser encryptor, never plaintext." as *u8) }
823 // AUTH events live in their own slot so the daemon's post-crypto verdict survives cs_handle2's banner reset.
824 let ae: i64 = ctx[CS_AUTH_EVENT]
825 if ae==CS_AE_SIGNED_IN { o = cu_banner(dst, o, 1, "Signed in" as *u8, "Your session is a signed token this browser holds — the server keeps no password, only an OPAQUE envelope nothing can be cracked from offline." as *u8) }
826 if ae==CS_AE_REGISTERED { o = cu_banner(dst, o, 1, "Account created — and you’re signed in" as *u8, "Keep your recovery phrase somewhere safe: it is the ONLY way back into this account, because we never stored your password." as *u8) }
827 if ae==CS_AE_SIGNED_OUT { o = cu_banner(dst, o, 0, "Signed out" as *u8, "Your session token is cleared from this browser. Your account and messages are untouched." as *u8) }
828 if ae==CS_AE_LOGIN_FAIL { o = cu_banner(dst, o, 2, "Sign-in failed" as *u8, "That handle and passphrase did not match. (We do not say which was wrong — that would leak whether a handle exists.)" as *u8) }
829 if ae==CS_AE_REG_TAKEN { o = cu_banner(dst, o, 2, "That handle is taken" as *u8, "Pick another. If it is yours, sign in instead." as *u8) }
830 if ae==CS_AE_REG_BAD { o = cu_banner(dst, o, 2, "Could not create the account" as *u8, "A handle is 1–24 characters of a–z, 0–9 or underscore; a passphrase is at least 8 characters." as *u8) }
831 if b==CS_B_CONTACT_ADDED { o = cu_banner(dst, o, 1, "Contact added" as *u8, "They are in your list. When they publish an end-to-end key you will be able to message them privately." as *u8) }
832 if b==CS_B_CONTACT_DUP { o = cu_banner(dst, o, 0, "Already in your contacts" as *u8, "Adding the same handle twice changes nothing — your list is a set, so it can never double-count." as *u8) }
833 if b==CS_B_CONTACT_BAD { o = cu_banner(dst, o, 2, "That handle is not valid" as *u8, "A handle is 1–24 characters of a–z, 0–9 or underscore." as *u8) }
834 if b==CS_B_CONTACT_SELF { o = cu_banner(dst, o, 2, "That’s you" as *u8, "You cannot add your own handle as a contact." as *u8) }
835 if b==CS_B_CONTACT_FULL { o = cu_banner(dst, o, 2, "Your contact list is full" as *u8, "Remove someone before adding another." as *u8) }
836 if b==CS_B_CONTACT_RM { o = cu_banner(dst, o, 0, "Contact removed" as *u8, "They are no longer in your list." as *u8) }
837 return o
838}
839
840func cs_foot(dst: *u8, off: i64) -> i64 {
841 return cu_foot(dst, off, "Safe & honest by construction: no adult–youth private messages · zero paywalled connection features · content-blind server · consent-only discovery. Measured head-to-head at <a href=\"/compare/connect\">/compare/connect</a>." as *u8)
842}
843
844// ---------------------------------------------------------------------------- pages
845
846func cs_page_home(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
847 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_HOME, "Connect — meet, learn a language, belong" as *u8)
848 o = cs_banner(ctx, dst, o)
849 o = cu_h1(dst, o, "Connect" as *u8, "A worldwide place to meet people through language, faith, and what you actually share — then reconnect with the ones you already knew. Free to connect, forever." as *u8)
850 o = cu_grid_open(dst, o)
851 o = cu_stat(dst, o, ctx[CS_NMSG], "Messages in this thread" as *u8)
852 o = cu_stat(dst, o, ctx[CS_EV1_GOING]+ctx[CS_EV2_ADULTS]+ctx[CS_EV2_MINORS], "People on event rosters" as *u8)
853 o = cu_stat(dst, o, 0, "Connection features behind a paywall" as *u8)
854 o = cu_grid_close(dst, o)
855 o = cu_h2(dst, o, "Where would you like to go?" as *u8)
856 o = cu_grid_open(dst, o)
857 o = cu_puts(dst, o, "<a class=\"card\" href=\"" as *u8); o = cu_puts(dst, o, bp)
858 o = cu_puts(dst, o, "/reconnect\"><h3>Reconnect</h3><p class=\"muted\">Find the missionaries who taught you, or the members and families you served. Only people you genuinely overlapped with — same mission, same area, same time.</p></a>" as *u8)
859 o = cu_puts(dst, o, "<a class=\"card\" href=\"" as *u8); o = cu_puts(dst, o, bp)
860 o = cu_puts(dst, o, "/partners\"><h3>Language partners</h3><p class=\"muted\">Matched with someone learning your language while you learn theirs. A one-sided pair scores zero — a real two-way trade or nothing.</p></a>" as *u8)
861 o = cu_puts(dst, o, "<a class=\"card\" href=\"" as *u8); o = cu_puts(dst, o, bp)
862 o = cu_puts(dst, o, "/chat\"><h3>Chat that teaches</h3><p class=\"muted\">Every message translated inline with the original kept beside it. Unlimited by construction — there is no quota in the software to run out of.</p></a>" as *u8)
863 o = cu_puts(dst, o, "<a class=\"card\" href=\"" as *u8); o = cu_puts(dst, o, bp)
864 o = cu_puts(dst, o, "/community\"><h3>Ward & branch community</h3><p class=\"muted\">A place to belong between Sundays. Community only — never callings, records, attendance, or donations.</p></a>" as *u8)
865 o = cu_puts(dst, o, "<a class=\"card\" href=\"" as *u8); o = cu_puts(dst, o, bp)
866 o = cu_puts(dst, o, "/events\"><h3>Real-world meetups</h3><p class=\"muted\">Gather in person, with a two-deep youth rule the software actually enforces. No organiser fee, no fee to RSVP.</p></a>" as *u8)
867 o = cu_puts(dst, o, "<a class=\"card\" href=\"/compare/connect\"><h3>The measured scorecard</h3><p class=\"muted\">How we compare against Tandem, HelloTalk, Meetup, and Discord — with the places we start behind stated plainly.</p></a>" as *u8)
868 o = cu_grid_close(dst, o)
869 return cs_foot(dst, o)
870}
871
872// ---- /partners: scores are COMPUTED by the shared match engine, never written by hand ----
873// Candidate table (synthetic, no real accounts). Language bits EN=1 ES=2 FR=4 DE=8 RU=16.
874// Interest bits MUSIC=1 SPORTS=2 COOK=4 TECH=8 ART=16.
875// YOU: age 30, teach EN(1), learn RU(16), interests MUSIC+COOK(5).
876const CS_ME_AGE: i64 = 30
877const CS_ME_LEARN: i64 = 16
878const CS_ME_TEACH: i64 = 1
879const CS_ME_INT: i64 = 5
880const CS_NCAND: i64 = 4
881
882func cs_cand_name(i: i64) -> *u8 {
883 if i==0 { return "Vera" as *u8 }
884 if i==1 { return "Piotr" as *u8 }
885 if i==2 { return "Lena" as *u8 }
886 return "Mia" as *u8
887}
888func cs_cand_meta(i: i64) -> *u8 {
889 if i==0 { return "Teaches Russian · learning English · shares your taste in music" as *u8 }
890 if i==1 { return "Teaches Russian · learning English · shares music and cooking" as *u8 }
891 if i==2 { return "Teaches Russian · learning <b>French</b> — and you cannot teach French" as *u8 }
892 return "Teaches Russian · learning English · <b>14 years old</b>" as *u8
893}
894func cs_cand_age(i: i64) -> i64 { if i==0 { return 27 } if i==1 { return 34 } if i==2 { return 29 } return 14 }
895func cs_cand_learn(i: i64) -> i64 { if i==2 { return 4 } return 1 } // Lena wants FRENCH, the others English
896func cs_cand_teach(i: i64) -> i64 { return 16 } // all four can teach Russian
897func cs_cand_int(i: i64) -> i64 { if i==0 { return 9 } if i==1 { return 13 } if i==2 { return 1 } return 5 }
898
899func cs_page_partners(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
900 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_PARTNERS, "Connect — language partners" as *u8)
901 o = cs_banner(ctx, dst, o)
902 o = cu_h1(dst, o, "Language partners" as *u8, "You teach English and want to learn Russian. These ranks are computed live by the same engine its own test suite verifies — an internal ordering, never a compatibility claim about a person." as *u8)
903
904 // score every candidate through the SHARED engine, then rank by the result
905 let w: *i64 = sys_mmap(CS_NCAND*8) as *i64
906 let idx: *i64 = sys_mmap(CS_NCAND*8) as *i64
907 var i: i64 = 0
908 while i < CS_NCAND {
909 w[i] = mx_match_weight(CS_ME_AGE, CS_ME_LEARN, CS_ME_TEACH, CS_ME_INT,
910 cs_cand_age(i), cs_cand_learn(i), cs_cand_teach(i), cs_cand_int(i), MX_LANE_LANG)
911 idx[i] = i
912 i = i + 1
913 }
914 // selection sort, highest weight first (tiny N, and the ORDER is engine output rather than authorship)
915 var a: i64 = 0
916 while a < CS_NCAND {
917 var best: i64 = a
918 var b: i64 = a + 1
919 while b < CS_NCAND {
920 if w[idx[b]] > w[idx[best]] { best = b }
921 b = b + 1
922 }
923 let t: i64 = idx[a]
924 idx[a] = idx[best]
925 idx[best] = t
926 a = a + 1
927 }
928
929 o = cu_rows_open(dst, o)
930 var k: i64 = 0
931 while k < CS_NCAND {
932 let c: i64 = idx[k]
933 let score: i64 = w[c]
934 let lbl: *u8 = sys_mmap(64)
935 var ln: i64 = cs_cat(lbl, 0, "rank " as *u8)
936 ln = cu_putn(lbl, ln, score)
937 lbl[ln] = 0 as u8
938 var kind: i64 = 1
939 if score == 0 { kind = 3 } else { if score < 40 { kind = 2 } }
940 o = cu_person(dst, o, cs_cand_name(c), cs_cand_meta(c), kind, lbl)
941 k = k + 1
942 }
943 o = cu_rows_close(dst, o)
944
945 o = cu_note(dst, o, "Lena scores <b>exactly zero</b>, not “low”. The engine takes the harmonic mean of both directions, so a pair that cannot teach each other collapses to nothing however much else they share — reciprocity is enforced by the arithmetic, not by a filter someone can later relax. Shared interests only modulate a pair that already works." as *u8)
946
947 // the safety wall, shown as a real result rather than a promise
948 let mia_lang: i64 = mx_match_weight(CS_ME_AGE, CS_ME_LEARN, CS_ME_TEACH, CS_ME_INT,
949 cs_cand_age(3), cs_cand_learn(3), cs_cand_teach(3), cs_cand_int(3), MX_LANE_LANG)
950 let mia_date: i64 = mx_match_weight(CS_ME_AGE, CS_ME_LEARN, CS_ME_TEACH, CS_ME_INT,
951 cs_cand_age(3), cs_cand_learn(3), cs_cand_teach(3), cs_cand_int(3), MX_LANE_DATE)
952 o = cu_card_open(dst, o)
953 o = cu_puts(dst, o, "<h3>The same person, two different lanes</h3><p class=\"muted\">Mia is 14. As a language partner the engine ranks her <b>" as *u8)
954 o = cu_putn(dst, o, mia_lang)
955 o = cu_puts(dst, o, "</b> — among the best matches here, and she stays fully reachable for learning. Asked for the romance lane the same call returns <b>" as *u8)
956 if mia_date < 0 { o = cu_puts(dst, o, "excluded" as *u8) } else { o = cu_putn(dst, o, mia_date) }
957 o = cu_puts(dst, o, "</b>.</p>" as *u8)
958 o = cu_card_close(dst, o)
959 o = cu_note(dst, o, "That is not a low score or a hidden result — the romance lane returns a negative sentinel that has no position on any ranking, in both directions, before any score is computed. An adult cannot reach a minor through it and a minor cannot reach an adult through it. Nothing about her profile can raise that number, because it is not a number." as *u8)
960 return cs_foot(dst, o)
961}
962
963// one chat bubble, rendered through the UI kit with the live translation beside the original.
964func cs_msg_render(ctx: *i64, i: i64, dst: *u8, off: i64) -> i64 {
965 let spk: *i64 = ctx[CS_SPK] as *i64
966 let txt: *u8 = ctx[CS_TXTA] as *u8
967 let corr: *u8 = ctx[CS_CORR] as *u8
968 let src: *u8 = (txt as i64 + i*CS_TXT) as *u8
969 var o: i64 = off
970 var own: i64 = 1
971 if spk[i]==1 { own = 0 }
972 if spk[i]==1 { o = cu_bubble_open(dst, o, "Vera" as *u8, own, src) } else { o = cu_bubble_open(dst, o, "You" as *u8, own, src) }
973 let tr: *u8 = sys_mmap(1024)
974 var tn: i64 = 0
975 if spk[i]==1 { tn = nx_multi_translate(src, cs_slen(src), MM_RU, MM_EN, tr, 1023) }
976 else { tn = nx_multi_translate(src, cs_slen(src), MM_EN, MM_RU, tr, 1023) }
977 tr[tn]=0 as u8
978 o = cu_bubble_tr(dst, o, tr)
979 let ci: *u8 = (corr as i64 + i*CS_TXT) as *u8
980 if ci[0]!=(0 as u8) { o = cu_bubble_corr(dst, o, ci) }
981 return cu_bubble_close(dst, o)
982}
983
984func cs_page_chat(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
985 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_CHAT, "Connect — chat with Vera" as *u8)
986 o = cs_banner(ctx, dst, o)
987 o = cu_h1(dst, o, "Chat with Vera" as *u8, "Every message is translated inline and the original is kept beside it, so the conversation is also the lesson." as *u8)
988 o = cu_thread_open(dst, o)
989 var i: i64=0
990 while i<ctx[CS_NMSG] { o=cs_msg_render(ctx, i, dst, o); i=i+1 }
991 o = cu_thread_close(dst, o)
992 o = cu_card_open(dst, o)
993 o = cu_puts(dst, o, "<h3>Say something</h3>" as *u8)
994 let act: *u8 = sys_mmap(256)
995 var an: i64 = cs_cat(act, 0, bp)
996 an = cs_cat(act, an, "/say" as *u8)
997 act[an]=0 as u8
998 o = cu_form_open(dst, o, act)
999 o = cu_input(dst, o, "text" as *u8, "Message" as *u8, "Try привет друг, спасибо, любовь…" as *u8, 100)
1000 o = cu_select_open(dst, o, "speaker" as *u8, "Speaking as" as *u8)
1001 o = cu_option(dst, o, "vera" as *u8, "Vera (Russian)" as *u8)
1002 o = cu_option(dst, o, "you" as *u8, "You (English)" as *u8)
1003 o = cu_select_close(dst, o)
1004 o = cu_submit(dst, o, "Send" as *u8)
1005 o = cu_form_close(dst, o)
1006 var cn: i64 = cs_cat(act, 0, bp)
1007 cn = cs_cat(act, cn, "/correct" as *u8)
1008 act[cn]=0 as u8
1009 o = cu_form_open(dst, o, act)
1010 o = cu_input(dst, o, "text" as *u8, "Gently correct the last message" as *u8, "привет, друг" as *u8, 100)
1011 o = cu_submit(dst, o, "Attach correction" as *u8)
1012 o = cu_form_close(dst, o)
1013 o = cu_card_close(dst, o)
1014 o = cu_note(dst, o, "Words the demo lexicon does not know pass through untouched — nothing is ever silently dropped. A correction attaches <i>beside</i> the original rather than overwriting it, because the mistake is the part you learn from. There is no daily translation quota anywhere in this software to run out of." as *u8)
1015
1016 // ---- the INTENT LANE for this specific relationship (the anti-creep law, made operable) ----
1017 let lane: i64 = ln_active_lane(ctx[CS_LANE_ME], ctx[CS_LANE_THEM], 1, 1, ctx[CS_BLOCKED])
1018 o = cu_h2(dst, o, "What this relationship is" as *u8)
1019 o = cu_card_open(dst, o)
1020 o = cu_puts(dst, o, "<p>Right now this relationship is <b>" as *u8)
1021 o = cu_puts(dst, o, ln_lane_name(lane))
1022 o = cu_puts(dst, o, "</b>.</p>" as *u8)
1023 o = cu_rows_open(dst, o)
1024 o = cu_puts(dst, o, "<li class=\"row\"><div class=\"who\"><b>You are open to</b></div>" as *u8)
1025 o = cu_badge(dst, o, 0, ln_lane_name(ctx[CS_LANE_ME]))
1026 o = cu_puts(dst, o, "</li><li class=\"row\"><div class=\"who\"><b>Vera is open to</b></div>" as *u8)
1027 o = cu_badge(dst, o, 0, ln_lane_name(ctx[CS_LANE_THEM]))
1028 o = cu_puts(dst, o, "</li><li class=\"row\"><div class=\"who\"><b>Therefore active</b></div>" as *u8)
1029 if ctx[CS_BLOCKED]==1 { o = cu_badge(dst, o, 3, ln_lane_name(lane)) } else { o = cu_badge(dst, o, 1, ln_lane_name(lane)) }
1030 o = cu_puts(dst, o, "</li>" as *u8)
1031 o = cu_rows_close(dst, o)
1032
1033 let lact: *u8 = sys_mmap(256)
1034 var lna: i64 = cs_cat(lact, 0, bp)
1035 lna = cs_cat(lact, lna, "/lane" as *u8)
1036 lact[lna]=0 as u8
1037 o = cu_form_open(dst, o, lact)
1038 o = cu_select_open(dst, o, "mine" as *u8, "You are open to" as *u8)
1039 o = cu_option(dst, o, "0" as *u8, "Language exchange" as *u8)
1040 o = cu_option(dst, o, "1" as *u8, "Friendship" as *u8)
1041 o = cu_option(dst, o, "2" as *u8, "Community" as *u8)
1042 o = cu_option(dst, o, "3" as *u8, "Dating" as *u8)
1043 o = cu_select_close(dst, o)
1044 o = cu_select_open(dst, o, "theirs" as *u8, "Vera is open to" as *u8)
1045 o = cu_option(dst, o, "0" as *u8, "Language exchange" as *u8)
1046 o = cu_option(dst, o, "1" as *u8, "Friendship" as *u8)
1047 o = cu_option(dst, o, "2" as *u8, "Community" as *u8)
1048 o = cu_option(dst, o, "3" as *u8, "Dating" as *u8)
1049 o = cu_select_close(dst, o)
1050 o = cu_submit(dst, o, "Update intent" as *u8)
1051 o = cu_form_close(dst, o)
1052
1053 var bna: i64 = cs_cat(lact, 0, bp)
1054 bna = cs_cat(lact, bna, "/block" as *u8)
1055 lact[bna]=0 as u8
1056 o = cu_form_open(dst, o, lact)
1057 if ctx[CS_BLOCKED]==1 {
1058 o = cu_puts(dst, o, "<input type=\"hidden\" name=\"set\" value=\"off\">" as *u8)
1059 o = cu_submit(dst, o, "Unblock" as *u8)
1060 } else {
1061 o = cu_puts(dst, o, "<input type=\"hidden\" name=\"set\" value=\"on\">" as *u8)
1062 o = cu_submit(dst, o, "Block Vera" as *u8)
1063 }
1064 o = cu_form_close(dst, o)
1065 o = cu_card_close(dst, o)
1066 o = cu_note(dst, o, "Set yourself to <b>Dating</b> while Vera stays on Language and watch the active lane refuse to move — a relationship sits at the level you have <i>both</i> opted into, so nobody can be escalated toward romance by someone else’s wishing. Blocking is the deliberate opposite: it takes effect instantly and needs nobody’s agreement. Hard to escalate, easy to leave. If either person were a minor the romance level would be unreachable no matter what both requested." as *u8)
1067 return cs_foot(dst, o)
1068}
1069
1070// ---- the LDS signature surface, driven by the SHARED reconnection library ----
1071// roster slot layout matches the library record: band, mission, areas, start, end, optin, token, confirms.
1072func cs_recon_person(dst: *u8, off: i64, me: *i64, name: *u8, meta: *u8, p: *i64, why: *u8) -> i64 {
1073 let score: i64 = rc_lane(me, p, RC_LANE_COMMUNITY)
1074 var o: i64 = off
1075 o = cu_puts(dst, o, "<li class=\"row\">" as *u8)
1076 o = cu_avatar(dst, o, name, 0)
1077 o = cu_puts(dst, o, "<div class=\"who\"><b>" as *u8)
1078 o = cu_puts(dst, o, name)
1079 o = cu_puts(dst, o, "</b><span>" as *u8)
1080 o = cu_puts(dst, o, meta)
1081 if score<=0 { o = cu_puts(dst, o, "<br>" as *u8); o = cu_puts(dst, o, why) }
1082 o = cu_puts(dst, o, "</span></div>" as *u8)
1083 if score>=60 { o = cu_badge(dst, o, 1, "strong match" as *u8) } else {
1084 if score>0 { o = cu_badge(dst, o, 2, "possible" as *u8) } else { o = cu_badge(dst, o, 0, "no match" as *u8) } }
1085 return cu_puts(dst, o, "</li>" as *u8)
1086}
1087
1088func cs_page_reconnect(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
1089 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_RECON, "Connect — reconnect" as *u8)
1090 o = cs_banner(ctx, dst, o)
1091 o = cu_h1(dst, o, "Reconnect" as *u8, "Find the missionaries who taught you, or the members and families you served. Only people you genuinely overlapped with are surfaced — same mission, shared area, and overlapping time are all required." as *u8)
1092
1093 // my record, built from this session's answers
1094 let me: *i64 = sys_mmap(RC_REC_SLOTS*8) as *i64
1095 rc_mk(me, RC_BAND_ADULT, ctx[CS_RC_MISSION], ctx[CS_RC_AREA],
1096 cs_year_to_month(ctx[CS_RC_FROM]), cs_year_to_month(ctx[CS_RC_TO]), ctx[CS_RC_CONSENT], 1, 2)
1097
1098 let act: *u8 = sys_mmap(256)
1099 var an: i64 = cs_cat(act, 0, bp)
1100 an = cs_cat(act, an, "/consent" as *u8)
1101 act[an]=0 as u8
1102
1103 o = cu_card_open(dst, o)
1104 o = cu_puts(dst, o, "<h3>Your consent</h3>" as *u8)
1105 if ctx[CS_RC_CONSENT]==1 {
1106 o = cu_puts(dst, o, "<p class=\"muted\">Reconnection discovery is <b>on</b>. People you actually overlapped with can find you, and you can see them.</p>" as *u8)
1107 o = cu_form_open(dst, o, act)
1108 o = cu_puts(dst, o, "<input type=\"hidden\" name=\"set\" value=\"off\">" as *u8)
1109 o = cu_submit(dst, o, "Turn discovery off" as *u8)
1110 o = cu_form_close(dst, o)
1111 } else {
1112 o = cu_puts(dst, o, "<p class=\"muted\">Reconnection discovery is <b>off</b>. Nothing is surfaced — not to you, and not about you.</p>" as *u8)
1113 o = cu_form_open(dst, o, act)
1114 o = cu_puts(dst, o, "<input type=\"hidden\" name=\"set\" value=\"on\">" as *u8)
1115 o = cu_submit(dst, o, "Turn discovery on" as *u8)
1116 o = cu_form_close(dst, o)
1117 }
1118 o = cu_card_close(dst, o)
1119
1120 if ctx[CS_RC_CONSENT]==0 {
1121 o = cu_empty(dst, o, "Nothing is shown while discovery is off. This is not a filter applied to a list we already built — without consent from both sides the search returns nothing at all." as *u8)
1122 o = cu_note(dst, o, "Religious belief and service history are special-category data. Consent is required from <b>both</b> people before either appears in the other’s results, and withdrawing it removes you immediately." as *u8)
1123 return cs_foot(dst, o)
1124 }
1125
1126 // the query
1127 var qn: i64 = cs_cat(act, 0, bp)
1128 qn = cs_cat(act, qn, "/find" as *u8)
1129 act[qn]=0 as u8
1130 o = cu_card_open(dst, o)
1131 o = cu_puts(dst, o, "<h3>Where and when were you there?</h3>" as *u8)
1132 o = cu_form_open(dst, o, act)
1133 o = cu_select_open(dst, o, "mission" as *u8, "Mission" as *u8)
1134 if ctx[CS_RC_MISSION]==7 { o = cu_puts(dst, o, "<option value=\"7\" selected>Argentina Rosario</option><option value=\"9\">Japan Kobe</option>" as *u8) }
1135 else { o = cu_puts(dst, o, "<option value=\"7\">Argentina Rosario</option><option value=\"9\" selected>Japan Kobe</option>" as *u8) }
1136 o = cu_select_close(dst, o)
1137 o = cu_select_open(dst, o, "area" as *u8, "Area" as *u8)
1138 if ctx[CS_RC_AREA]==1 { o = cu_puts(dst, o, "<option value=\"1\" selected>Centro</option>" as *u8) } else { o = cu_puts(dst, o, "<option value=\"1\">Centro</option>" as *u8) }
1139 if ctx[CS_RC_AREA]==2 { o = cu_puts(dst, o, "<option value=\"2\" selected>Fisherton</option>" as *u8) } else { o = cu_puts(dst, o, "<option value=\"2\">Fisherton</option>" as *u8) }
1140 if ctx[CS_RC_AREA]==8 { o = cu_puts(dst, o, "<option value=\"8\" selected>Funes</option>" as *u8) } else { o = cu_puts(dst, o, "<option value=\"8\">Funes</option>" as *u8) }
1141 o = cu_select_close(dst, o)
1142 o = cu_input(dst, o, "from" as *u8, "From year" as *u8, "2019" as *u8, 4)
1143 o = cu_input(dst, o, "to" as *u8, "To year" as *u8, "2021" as *u8, 4)
1144 o = cu_submit(dst, o, "Find people" as *u8)
1145 o = cu_form_close(dst, o)
1146 o = cu_puts(dst, o, "<p class=\"note\">Searching " as *u8)
1147 if ctx[CS_RC_MISSION]==7 { o = cu_puts(dst, o, "<b>Argentina Rosario</b>" as *u8) } else { o = cu_puts(dst, o, "<b>Japan Kobe</b>" as *u8) }
1148 o = cu_puts(dst, o, ", area " as *u8)
1149 if ctx[CS_RC_AREA]==1 { o = cu_puts(dst, o, "<b>Centro</b>" as *u8) } else {
1150 if ctx[CS_RC_AREA]==2 { o = cu_puts(dst, o, "<b>Fisherton</b>" as *u8) } else { o = cu_puts(dst, o, "<b>Funes</b>" as *u8) } }
1151 o = cu_puts(dst, o, ", " as *u8)
1152 o = cu_putn(dst, o, ctx[CS_RC_FROM])
1153 o = cu_puts(dst, o, "–" as *u8)
1154 o = cu_putn(dst, o, ctx[CS_RC_TO])
1155 o = cu_puts(dst, o, ".</p>" as *u8)
1156 o = cu_card_close(dst, o)
1157
1158 // the roster -- synthetic, zero real personal data. Each entry exists to make one law visible.
1159 let p1: *i64 = sys_mmap(RC_REC_SLOTS*8) as *i64
1160 let p2: *i64 = sys_mmap(RC_REC_SLOTS*8) as *i64
1161 let p3: *i64 = sys_mmap(RC_REC_SLOTS*8) as *i64
1162 let p4: *i64 = sys_mmap(RC_REC_SLOTS*8) as *i64
1163 let p5: *i64 = sys_mmap(RC_REC_SLOTS*8) as *i64
1164 let p6: *i64 = sys_mmap(RC_REC_SLOTS*8) as *i64
1165 // Sister Cortez: same mission+area+time AND a mutual "I remember you" -> ranks highest
1166 rc_mk(p2, RC_BAND_ADULT, 7, 2, cs_year_to_month(2019), cs_year_to_month(2021), 1, 2, 1)
1167 // Elder Hansen: same mission, overlapping area set and time, no confirmation -> inferred
1168 rc_mk(p1, RC_BAND_ADULT, 7, 3, cs_year_to_month(2018), cs_year_to_month(2020), 1, 4, 0)
1169 // Brother Alvarez: same mission and time, DIFFERENT area
1170 rc_mk(p3, RC_BAND_ADULT, 7, 8, cs_year_to_month(2019), cs_year_to_month(2021), 1, 8, 0)
1171 // Elder Whitfield: same mission and area, but served AFTER you left
1172 rc_mk(p4, RC_BAND_ADULT, 7, 2, cs_year_to_month(2024), cs_year_to_month(2026), 1, 16, 0)
1173 // Sister Ono: different mission entirely
1174 rc_mk(p5, RC_BAND_ADULT, 9, 2, cs_year_to_month(2019), cs_year_to_month(2021), 1, 32, 0)
1175 // Brother Reyes: a real overlap, but he has NOT opted in
1176 rc_mk(p6, RC_BAND_ADULT, 7, 2, cs_year_to_month(2019), cs_year_to_month(2021), 0, 64, 0)
1177
1178 o = cu_h2(dst, o, "People you may have known" as *u8)
1179 o = cu_rows_open(dst, o)
1180 o = cs_recon_person(dst, o, me, "Sister Cortez" as *u8, "Argentina Rosario · Fisherton · 2019–2021 · you both confirmed remembering each other" as *u8, p2, "" as *u8)
1181 o = cs_recon_person(dst, o, me, "Elder Hansen" as *u8, "Argentina Rosario · Centro and Fisherton · 2018–2020" as *u8, p1, "" as *u8)
1182 o = cs_recon_person(dst, o, me, "Brother Alvarez" as *u8, "Argentina Rosario · Funes · 2019–2021" as *u8, p3, "Same mission and the same years, but a different area — you were never in the same place." as *u8)
1183 o = cs_recon_person(dst, o, me, "Elder Whitfield" as *u8, "Argentina Rosario · Fisherton · 2024–2026" as *u8, p4, "Served your area, but after you left. A later missionary is not the one who taught you." as *u8)
1184 o = cs_recon_person(dst, o, me, "Sister Ono" as *u8, "Japan Kobe · 2019–2021" as *u8, p5, "A different mission." as *u8)
1185 o = cs_recon_person(dst, o, me, "Brother Reyes" as *u8, "Argentina Rosario · Fisherton · 2019–2021" as *u8, p6, "You did overlap — but he has not turned on reconnection discovery, so he is not surfaced. His choice wins over your search." as *u8)
1186 o = cu_rows_close(dst, o)
1187 o = cu_note(dst, o, "The people who do <b>not</b> appear are the point. A matcher that only checked “same mission” would hand you Brother Alvarez and Elder Whitfield as if they were old friends. Requiring a shared area <i>and</i> overlapping time is what makes the result honest — and Brother Reyes stays hidden no matter how strong the overlap, because consent is not a ranking signal, it is a precondition." as *u8)
1188 o = cu_note(dst, o, "If one of these people were a youth when you taught them, the friendship and community lanes stay open and the romance lane is unreachable in both directions — not discouraged, unreachable." as *u8)
1189 return cs_foot(dst, o)
1190}
1191
1192func cs_page_community(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
1193 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_COMMUNITY, "Connect — community" as *u8)
1194 o = cs_banner(ctx, dst, o)
1195 o = cu_h1(dst, o, "Ward & branch community" as *u8, "A place to belong between Sundays — community only. This group holds no callings, no membership records, no attendance, and no donations, by construction." as *u8)
1196 let act: *u8 = sys_mmap(256)
1197 var an: i64 = cs_cat(act, 0, bp)
1198 an = cs_cat(act, an, "/join" as *u8)
1199 act[an]=0 as u8
1200 // faith consent (Art.9) -- joining a ward group reveals religious belief
1201 let fact: *u8 = sys_mmap(256)
1202 var fn: i64 = cs_cat(fact, 0, bp)
1203 fn = cs_cat(fact, fn, "/faith" as *u8)
1204 fact[fn]=0 as u8
1205 o = cu_card_open(dst, o)
1206 o = cu_puts(dst, o, "<h3>Faith consent</h3>" as *u8)
1207 if ctx[CS_FAITH_CONSENT]==1 {
1208 o = cu_puts(dst, o, "<p class=\"muted\">Recorded. You can join faith community groups, and withdrawing removes you from them.</p>" as *u8)
1209 o = cu_form_open(dst, o, fact)
1210 o = cu_puts(dst, o, "<input type=\"hidden\" name=\"set\" value=\"off\">" as *u8)
1211 o = cu_submit(dst, o, "Withdraw faith consent" as *u8)
1212 o = cu_form_close(dst, o)
1213 } else {
1214 o = cu_puts(dst, o, "<p class=\"muted\">Not on file. Religious belief is special-category data, so nothing about faith is recorded until you say so.</p>" as *u8)
1215 o = cu_form_open(dst, o, fact)
1216 o = cu_puts(dst, o, "<input type=\"hidden\" name=\"set\" value=\"on\">" as *u8)
1217 o = cu_submit(dst, o, "Give faith consent" as *u8)
1218 o = cu_form_close(dst, o)
1219 }
1220 o = cu_card_close(dst, o)
1221
1222 o = cu_card_open(dst, o)
1223 o = cu_puts(dst, o, "<h3>Fisherton Ward — community group</h3>" as *u8)
1224 if ctx[CS_WARD]==1 {
1225 o = cu_puts(dst, o, "<p class=\"muted\">You are on this group’s roster.</p>" as *u8)
1226 } else {
1227 o = cu_puts(dst, o, "<p class=\"muted\">Open to anyone who feels at home here — members, friends, and visitors alike.</p>" as *u8)
1228 o = cu_form_open(dst, o, act)
1229 o = cu_submit(dst, o, "Join the group" as *u8)
1230 o = cu_form_close(dst, o)
1231 if ctx[CS_FAITH_CONSENT]==0 { o = cu_note(dst, o, "Without faith consent above, this join will be <b>refused</b> — try it and see." as *u8) }
1232 }
1233 o = cu_card_close(dst, o)
1234
1235 // THE BOUNDARY, checked live by the registry rather than asserted in copy
1236 let ours: *i64 = sys_mmap(FR_MAX*8) as *i64
1237 ours[0]=WD_CLASS_SAFE; ours[1]=WD_CLASS_SAFE; ours[2]=WD_CLASS_SAFE; ours[3]=WD_CLASS_SAFE; ours[4]=WD_CLASS_SAFE
1238 let ours_bad: i64 = wd_forbidden_count(ours, 5)
1239 let admin: *i64 = sys_mmap(FR_MAX*8) as *i64
1240 admin[0]=WD_CLASS_SAFE; admin[1]=WD_CLASS_SAFE; admin[2]=WD_CLASS_ATTENDANCE; admin[3]=WD_CLASS_CALLING; admin[4]=WD_CLASS_DONATION
1241 let admin_bad: i64 = wd_forbidden_count(admin, 5)
1242 o = cu_h2(dst, o, "What this group is allowed to hold" as *u8)
1243 o = cu_rows_open(dst, o)
1244 o = cu_person(dst, o, "Locality" as *u8, "Which ward or branch this group belongs to" as *u8, 1, "community" as *u8)
1245 o = cu_person(dst, o, "Display name" as *u8, "What the group calls itself" as *u8, 1, "community" as *u8)
1246 o = cu_person(dst, o, "Member list" as *u8, "People who chose to join" as *u8, 1, "community" as *u8)
1247 o = cu_person(dst, o, "Youth channel flag" as *u8, "Whether a two-deep youth channel exists" as *u8, 1, "community" as *u8)
1248 o = cu_person(dst, o, "Event link" as *u8, "A pointer to a meetup" as *u8, 1, "community" as *u8)
1249 o = cu_rows_close(dst, o)
1250 o = cu_card_open(dst, o)
1251 o = cu_puts(dst, o, "<p>Fields in this schema that a congregation-administration system would need: <b>" as *u8)
1252 o = cu_putn(dst, o, ours_bad)
1253 o = cu_puts(dst, o, "</b>. Run the same checker against a ward-<i>admin</i> model carrying attendance, callings and donations and it reports <b>" as *u8)
1254 o = cu_putn(dst, o, admin_bad)
1255 o = cu_puts(dst, o, "</b> — so the check is doing real work, not always answering zero.</p>" as *u8)
1256 o = cu_card_close(dst, o)
1257 o = cu_note(dst, o, "The boundary is <b>data, not a promise</b>. Every field this group may hold carries a class, and the community layer accepts only one of them. Callings, ordinances, attendance and donations are not forbidden by policy — there is no field of that class in the registry to put them in, and a proposed one is refused before it ships. There is nothing here for an administrator to misuse because there is nothing here to misuse." as *u8)
1258
1259 o = cu_h2(dst, o, "Where should the language circle meet on Saturday?" as *u8)
1260 // The tally is COMPUTED by the gated ballot engine. Three people in the circle have already voted; this
1261 // session's own ballot is cast through the same pl_vote, which REPLACES an existing ballot rather than
1262 // appending, so the count cannot inflate no matter how many times anyone votes.
1263 let bv: *i64 = sys_mmap(PL_MAXV*8) as *i64
1264 let bc: *i64 = sys_mmap(PL_MAXV*8) as *i64
1265 let nb: *i64 = sys_mmap(8) as *i64
1266 let pmeta: *i64 = sys_mmap(8*8) as *i64
1267 nb[0]=0
1268 pmeta[0]=0 // open
1269 pmeta[1]=3 // three options
1270 pmeta[2]=1 // membership range lo
1271 pmeta[3]=99 // membership range hi
1272 pl_vote(bv, bc, nb, pmeta, 1, 0)
1273 pl_vote(bv, bc, nb, pmeta, 2, 0)
1274 pl_vote(bv, bc, nb, pmeta, 3, 1)
1275 if ctx[CS_POLL]>0 { pl_vote(bv, bc, nb, pmeta, 99, ctx[CS_POLL]-1) }
1276 let c1: i64 = pl_count(bc, nb, 0)
1277 let c2: i64 = pl_count(bc, nb, 1)
1278 let c3: i64 = pl_count(bc, nb, 2)
1279 var total: i64 = c1+c2+c3
1280 o = cu_card_open(dst, o)
1281 o = cu_meter(dst, o, "Library" as *u8, c1, total)
1282 o = cu_meter(dst, o, "Riverside park" as *u8, c2, total)
1283 o = cu_meter(dst, o, "Cafe Rosa" as *u8, c3, total)
1284 var vn: i64 = cs_cat(act, 0, bp)
1285 vn = cs_cat(act, vn, "/vote" as *u8)
1286 act[vn]=0 as u8
1287 o = cu_form_open(dst, o, act)
1288 o = cu_select_open(dst, o, "opt" as *u8, "Your vote" as *u8)
1289 o = cu_option(dst, o, "1" as *u8, "Library" as *u8)
1290 o = cu_option(dst, o, "2" as *u8, "Riverside park" as *u8)
1291 o = cu_option(dst, o, "3" as *u8, "Cafe Rosa" as *u8)
1292 o = cu_select_close(dst, o)
1293 o = cu_submit(dst, o, "Vote" as *u8)
1294 o = cu_form_close(dst, o)
1295 o = cu_card_close(dst, o)
1296 o = cu_note(dst, o, "Voting again <b>replaces</b> your ballot instead of adding to the tally. One voter, one ballot — so the count cannot be inflated by anyone, including us." as *u8)
1297
1298 // ---- who may do what in a group, rendered as LIVE verdicts from the permission engine ----
1299 let gmeta: *i64 = sys_mmap(8*8) as *i64
1300 let gmem: *i64 = sys_mmap(256*8) as *i64
1301 cs_grp_seed(gmeta, gmem, MODE_GROUP)
1302 let q1: i64 = grp_remove(gmeta, gmem, 2, 4)
1303 cs_grp_seed(gmeta, gmem, MODE_GROUP)
1304 let q2: i64 = grp_remove(gmeta, gmem, 2, 3)
1305 cs_grp_seed(gmeta, gmem, MODE_GROUP)
1306 let q3: i64 = grp_remove(gmeta, gmem, 2, 1)
1307 cs_grp_seed(gmeta, gmem, MODE_GROUP)
1308 let q4: i64 = grp_remove(gmeta, gmem, 4, 2)
1309 cs_grp_seed(gmeta, gmem, MODE_GROUP)
1310 let q5: i64 = grp_can_post(gmeta, gmem, 4)
1311 cs_grp_seed(gmeta, gmem, MODE_CHANNEL)
1312 let q6: i64 = grp_can_post(gmeta, gmem, 4)
1313 cs_grp_seed(gmeta, gmem, MODE_CHANNEL)
1314 let q7: i64 = grp_can_post(gmeta, gmem, 2)
1315 o = cu_h2(dst, o, "Who may do what here" as *u8)
1316 o = cu_rows_open(dst, o)
1317 o = cs_role_row(dst, o, "A group admin removes an ordinary member" as *u8, q1)
1318 o = cs_role_row(dst, o, "An admin removes a fellow admin" as *u8, q2)
1319 o = cs_role_row(dst, o, "An admin removes the group’s owner" as *u8, q3)
1320 o = cs_role_row(dst, o, "An ordinary member removes an admin" as *u8, q4)
1321 o = cs_role_row(dst, o, "A member posts in a discussion group" as *u8, q5)
1322 o = cs_role_row(dst, o, "A member posts in an announcement channel" as *u8, q6)
1323 o = cs_role_row(dst, o, "An admin posts in an announcement channel" as *u8, q7)
1324 o = cu_rows_close(dst, o)
1325 o = cu_note(dst, o, "Every row is a live answer from the same permission engine the group runs on. The owner cannot be removed by anyone, and an admin cannot remove a peer — so there is no sequence of moves by which a group is taken from the person who started it. An announcement channel is read-only for members by construction, not by a setting somebody can flip." as *u8)
1326 return cs_foot(dst, o)
1327}
1328
1329// ---- the events world, rebuilt per request so the LIVE admission runs through ev_rsvp ----
1330// The page used to decide admission with a hand-written check that had NO CAPACITY AT ALL. This seeds a real
1331// events world from the session's persisted counters and hands the decision to the gated engine, which
1332// enforces membership, idempotency, the categorical youth wall and capacity IN THAT ORDER.
1333const CS_EV_PICNIC: i64 = 0
1334const CS_EV_YOUTH: i64 = 1
1335const CS_EV_PICNIC_CAP: i64 = 6
1336const CS_EV_YOUTH_CAP: i64 = 8
1337const CS_EV_GROUP: i64 = 1
1338// The person the RSVP form acts as. BOTH must be inside the seeded roster below, or ev_rsvp correctly
1339// returns -1 (not a group member) and the form appears to do nothing.
1340const CS_EV_NEW_ADULT: i64 = 30
1341const CS_EV_NEW_MINOR: i64 = 130
1342const CS_EV_ROSTER: i64 = 30 // adults 1..30, youth 101..130 (must stay <= MB_MAX/UA_MAX halves)
1343
1344func cs_ev_world(ctx: *i64) -> *i64 {
1345 let e: *i64 = sys_mmap(16*8) as *i64
1346 let rsev: *i64 = sys_mmap(RS_MAX*8) as *i64
1347 let rsus: *i64 = sys_mmap(RS_MAX*8) as *i64
1348 let rsac: *i64 = sys_mmap(RS_MAX*8) as *i64
1349 let nrs: *i64 = sys_mmap(8) as *i64
1350 let evgrp: *i64 = sys_mmap(EV_MAX*8) as *i64
1351 let evcap: *i64 = sys_mmap(EV_MAX*8) as *i64
1352 let evyth: *i64 = sys_mmap(EV_MAX*8) as *i64
1353 let mbgrp: *i64 = sys_mmap(MB_MAX*8) as *i64
1354 let mbus: *i64 = sys_mmap(MB_MAX*8) as *i64
1355 let nmb: *i64 = sys_mmap(8) as *i64
1356 let uaus: *i64 = sys_mmap(UA_MAX*8) as *i64
1357 let uamin: *i64 = sys_mmap(UA_MAX*8) as *i64
1358 let nua: *i64 = sys_mmap(8) as *i64
1359 e[CX_RSEV]=rsev as i64; e[CX_RSUS]=rsus as i64; e[CX_RSAC]=rsac as i64; e[CX_NRS]=nrs as i64
1360 e[CX_EVGRP]=evgrp as i64; e[CX_EVCAP]=evcap as i64; e[CX_EVYTH]=evyth as i64
1361 e[CX_MBGRP]=mbgrp as i64; e[CX_MBUS]=mbus as i64; e[CX_NMB]=nmb as i64
1362 e[CX_UAUS]=uaus as i64; e[CX_UAMIN]=uamin as i64; e[CX_NUA]=nua as i64
1363 // two events in one community group
1364 evgrp[CS_EV_PICNIC]=CS_EV_GROUP; evcap[CS_EV_PICNIC]=CS_EV_PICNIC_CAP; evyth[CS_EV_PICNIC]=0
1365 evgrp[CS_EV_YOUTH]=CS_EV_GROUP; evcap[CS_EV_YOUTH]=CS_EV_YOUTH_CAP; evyth[CS_EV_YOUTH]=1
1366 // roster of people: adults 1..20 (band 0), youth 101..120 (band 1); all in the group
1367 var i: i64=0
1368 var m: i64=0
1369 var u: i64=0
1370 while i<CS_EV_ROSTER {
1371 mbgrp[m]=CS_EV_GROUP; mbus[m]=1+i; m=m+1
1372 uaus[u]=1+i; uamin[u]=0; u=u+1
1373 i=i+1
1374 }
1375 i=0
1376 while i<CS_EV_ROSTER {
1377 mbgrp[m]=CS_EV_GROUP; mbus[m]=101+i; m=m+1
1378 uaus[u]=101+i; uamin[u]=1; u=u+1
1379 i=i+1
1380 }
1381 nmb[0]=m
1382 nua[0]=u
1383 // replay this session's persisted attendance as real RSVP rows
1384 var k: i64=0
1385 i=0
1386 while i<ctx[CS_EV1_GOING] { rsev[k]=CS_EV_PICNIC; rsus[k]=1+i; rsac[k]=1; k=k+1; i=i+1 }
1387 i=0
1388 while i<ctx[CS_EV2_ADULTS] { rsev[k]=CS_EV_YOUTH; rsus[k]=11+i; rsac[k]=1; k=k+1; i=i+1 }
1389 i=0
1390 while i<ctx[CS_EV2_MINORS] { rsev[k]=CS_EV_YOUTH; rsus[k]=101+i; rsac[k]=1; k=k+1; i=i+1 }
1391 nrs[0]=k
1392 return e
1393}
1394
1395// a demo group with a real role ladder: owner 1, admins 2 and 3, ordinary member 4. Re-seeded before every
1396// probe because grp_remove genuinely mutates the roster -- the panel must observe, not alter.
1397func cs_grp_seed(gmeta: *i64, gmem: *i64, mode: i64) -> i64 {
1398 grp_create(gmeta, gmem, 1, mode, LANE_LANG)
1399 grp_add(gmeta, gmem, 1, 2, 0)
1400 grp_add(gmeta, gmem, 1, 3, 0)
1401 grp_add(gmeta, gmem, 1, 4, 0)
1402 grp_promote(gmeta, gmem, 1, 2, ROLE_ADMIN)
1403 grp_promote(gmeta, gmem, 1, 3, ROLE_ADMIN)
1404 return 0
1405}
1406// one permission row rendered as the engine's live verdict.
1407func cs_role_row(dst: *u8, off: i64, label: *u8, ok: i64) -> i64 {
1408 var o: i64 = cu_puts(dst, off, "<li class=\"row\"><div class=\"who\"><b>" as *u8)
1409 o = cu_puts(dst, o, label)
1410 o = cu_puts(dst, o, "</b></div>" as *u8)
1411 if ok == 1 { o = cu_badge(dst, o, 1, "allowed" as *u8) } else { o = cu_badge(dst, o, 3, "blocked" as *u8) }
1412 return cu_puts(dst, o, "</li>" as *u8)
1413}
1414
1415// one row of the youth-safety wall, showing the LIVE verdict for a real pair of participants.
1416func cs_wall_row(dst: *u8, off: i64, label: *u8, a: i64, b: i64, guardian: i64, ctxk: i64, adults: i64) -> i64 {
1417 let ok: i64 = yw_may_channel(a, b, guardian, ctxk, adults)
1418 var o: i64 = cu_puts(dst, off, "<li class=\"row\"><div class=\"who\"><b>" as *u8)
1419 o = cu_puts(dst, o, label)
1420 o = cu_puts(dst, o, "</b></div>" as *u8)
1421 if ok == 1 { o = cu_badge(dst, o, 1, "allowed" as *u8) } else { o = cu_badge(dst, o, 3, "blocked" as *u8) }
1422 return cu_puts(dst, o, "</li>" as *u8)
1423}
1424
1425func cs_page_events(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
1426 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_EVENTS, "Connect — events" as *u8)
1427 o = cs_banner(ctx, dst, o)
1428 o = cu_h1(dst, o, "Real-world meetups" as *u8, "Gather in person around something you share. No organiser fee and no fee to RSVP — ever." as *u8)
1429 o = cu_card_open(dst, o)
1430 o = cu_puts(dst, o, "<h3>Park picnic</h3><p class=\"muted\">Adults only · Saturday afternoon · " as *u8)
1431 o = cu_putn(dst, o, CS_EV_PICNIC_CAP)
1432 o = cu_puts(dst, o, " seats</p>" as *u8)
1433 o = cu_meter(dst, o, "Going" as *u8, ctx[CS_EV1_GOING], CS_EV_PICNIC_CAP)
1434 if ctx[CS_EV1_GOING] >= CS_EV_PICNIC_CAP { o = cu_badge(dst, o, 3, "full" as *u8) }
1435 o = cu_card_close(dst, o)
1436 o = cu_card_open(dst, o)
1437 o = cu_puts(dst, o, "<h3>Youth game night</h3><p class=\"muted\">Youth · requires two adult leaders present · " as *u8)
1438 o = cu_putn(dst, o, CS_EV_YOUTH_CAP)
1439 o = cu_puts(dst, o, " seats</p>" as *u8)
1440 o = cu_meter(dst, o, "Adults" as *u8, ctx[CS_EV2_ADULTS], CS_EV_YOUTH_CAP)
1441 o = cu_meter(dst, o, "Youth" as *u8, ctx[CS_EV2_MINORS], CS_EV_YOUTH_CAP)
1442 // The badge asks the WALL, it does not re-derive the answer. The rule is NAMED in BOTH states on
1443 // purpose: if "two-deep" only appeared once it was satisfied, the visitor who is actually blocked --
1444 // the one who needs to understand why -- would never see it.
1445 if yw_may_channel(YW_BAND_ADULT, YW_BAND_MINOR, 0, YW_CTX_GROUP, ctx[CS_EV2_ADULTS])==1 {
1446 o = cu_badge(dst, o, 1, "two-deep satisfied — youth may join" as *u8)
1447 } else {
1448 o = cu_badge(dst, o, 2, "two-deep required — youth cannot join yet" as *u8)
1449 }
1450 o = cu_card_close(dst, o)
1451 let act: *u8 = sys_mmap(256)
1452 var an: i64 = cs_cat(act, 0, bp)
1453 an = cs_cat(act, an, "/rsvp" as *u8)
1454 act[an]=0 as u8
1455 o = cu_card_open(dst, o)
1456 o = cu_puts(dst, o, "<h3>RSVP</h3>" as *u8)
1457 o = cu_form_open(dst, o, act)
1458 o = cu_select_open(dst, o, "who" as *u8, "Who is coming" as *u8)
1459 o = cu_option(dst, o, "adult" as *u8, "Ana (adult leader)" as *u8)
1460 o = cu_option(dst, o, "minor" as *u8, "Max (youth, 14)" as *u8)
1461 o = cu_select_close(dst, o)
1462 o = cu_select_open(dst, o, "ev" as *u8, "To which event" as *u8)
1463 o = cu_option(dst, o, "youth" as *u8, "Youth game night" as *u8)
1464 o = cu_option(dst, o, "picnic" as *u8, "Park picnic" as *u8)
1465 o = cu_select_close(dst, o)
1466 o = cu_submit(dst, o, "RSVP" as *u8)
1467 o = cu_form_close(dst, o)
1468 o = cu_card_close(dst, o)
1469 o = cu_note(dst, o, "A youth may join only once two adults are already on the roster, and the check runs <b>before</b> capacity — a nearly-full event can never be the reason a safety rule is skipped. Try adding Max first and watch the software refuse." as *u8)
1470
1471 // The wall itself, rendered as LIVE ANSWERS rather than described in prose. A safety rule nobody can
1472 // inspect is a rule nobody can trust, so each row below is a real call into the same function that
1473 // guards the RSVP button above.
1474 o = cu_h2(dst, o, "Who can reach whom" as *u8)
1475 o = cu_rows_open(dst, o)
1476 o = cs_wall_row(dst, o, "Two youth, privately" as *u8, YW_BAND_MINOR, YW_BAND_MINOR, 0, YW_CTX_DM, 0)
1477 o = cs_wall_row(dst, o, "Two adults, privately" as *u8, YW_BAND_ADULT, YW_BAND_ADULT, 0, YW_CTX_DM, 0)
1478 o = cs_wall_row(dst, o, "A parent and their own youth, privately" as *u8, YW_BAND_ADULT, YW_BAND_MINOR, 1, YW_CTX_DM, 0)
1479 o = cs_wall_row(dst, o, "An adult and a youth — visible group, two adults present" as *u8, YW_BAND_ADULT, YW_BAND_MINOR, 0, YW_CTX_GROUP, 2)
1480 o = cs_wall_row(dst, o, "An adult and a youth — group with only one adult" as *u8, YW_BAND_ADULT, YW_BAND_MINOR, 0, YW_CTX_GROUP, 1)
1481 o = cs_wall_row(dst, o, "An adult and a youth — private one-to-one" as *u8, YW_BAND_ADULT, YW_BAND_MINOR, 0, YW_CTX_DM, 0)
1482 o = cu_rows_close(dst, o)
1483 o = cu_note(dst, o, "Youth talk to each other freely and parents are never blocked — the wall is narrow on purpose, because a safety rule that gets in everyone’s way is one people route around. Only the last three rows are constrained, and the final one — a private adult-to-youth message — is the one Meetup, Tandem, Match and Facebook all permit by default." as *u8)
1484 return cs_foot(dst, o)
1485}
1486
1487// the content-blind inbox: shows the ciphertext the server RECEIVED from the browser encryptor. The server
1488// has no key and cannot read any of it -- this page proves it by displaying exactly what it holds: ciphertext.
1489func cs_page_secure(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
1490 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_SECURE, "Connect — sealed inbox (content-blind)" as *u8)
1491 o = cs_banner(ctx, dst, o)
1492 o = cu_h1(dst, o, "Sealed inbox" as *u8, "Everything below arrived already encrypted by the sender’s browser. The server stored it as-is — it holds no key, so this is all it can ever see." as *u8)
1493 o = cu_note(dst, o, "Compose & encrypt in the browser at <a href=\"/synth/connect_e2e\">/synth/connect_e2e</a> — it seals with sovereign RFC-8439 ChaCha20 (WASM) and POSTs only ciphertext here." as *u8)
1494 let ctl: *u8 = ctx[CS_CT] as *u8
1495 if ctx[CS_NCT]<=0 {
1496 o = cu_empty(dst, o, "No sealed messages yet. When the browser encryptor sends one, its ciphertext (never its plaintext) appears here." as *u8)
1497 return cs_foot(dst, o)
1498 }
1499 o = cu_h2(dst, o, "Ciphertext the server holds" as *u8)
1500 o = cu_rows_open(dst, o)
1501 var i: i64=0
1502 while i<ctx[CS_NCT] {
1503 let ct: *u8 = (ctl as i64 + i*CS_CTHEX) as *u8
1504 o = cu_puts(dst, o, "<li class=\"row\"><div class=\"who\"><span class=\"mono\" style=\"word-break:break-all\">" as *u8)
1505 o = cu_esc(dst, o, ct)
1506 o = cu_puts(dst, o, "</span></div>" as *u8)
1507 o = cu_badge(dst, o, 0, "ciphertext" as *u8)
1508 o = cu_puts(dst, o, "</li>" as *u8)
1509 i=i+1
1510 }
1511 o = cu_rows_close(dst, o)
1512 o = cu_note(dst, o, "This is the whole point of content-blind messaging: even a fully-compromised server, or one served a legal demand, can hand over only these opaque bytes. Decryption happens in the recipient’s browser with a key the server never receives." as *u8)
1513 return cs_foot(dst, o)
1514}
1515
1516// ---------------------------------------------------------------------------- auth pages
1517
1518// SIGN IN: OPAQUE aPAKE. The form posts handle + passphrase; the daemon runs the aPAKE and mints a signed
1519// no-cookie-equivalent token it stores in a cookie. If ALREADY signed in, the page says so (idempotent).
1520func cs_page_login(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
1521 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_LOGIN, "Connect — sign in" as *u8)
1522 o = cs_banner(ctx, dst, o)
1523 if ctx[CS_AUTH]==1 {
1524 o = cu_h1(dst, o, "You’re signed in" as *u8, "Your identity on Connect is your handle, and it is already active in this browser." as *u8)
1525 o = cu_note(dst, o, "Go to your <a href=\"/connect/account\">account</a> to manage your keys or sign out." as *u8)
1526 return cs_foot(dst, o)
1527 }
1528 o = cu_h1(dst, o, "Sign in" as *u8, "Your passphrase proves who you are without ever being stored: the server keeps only an OPAQUE envelope, so a breach reveals nothing an attacker can crack offline." as *u8)
1529 o = cu_form_open(dst, o, "/connect/login" as *u8)
1530 o = cu_input(dst, o, "handle" as *u8, "Handle" as *u8, "your handle" as *u8, 24)
1531 o = cu_input_typed(dst, o, "pw" as *u8, "Passphrase" as *u8, "your passphrase" as *u8, 256, "password" as *u8, "current-password" as *u8)
1532 o = cu_submit(dst, o, "Sign in" as *u8)
1533 o = cu_form_close(dst, o)
1534 o = cu_note(dst, o, "No account yet? <a href=\"/connect/register\">Create one</a>. Forgot your passphrase? Your 24-word recovery phrase is the way back — it is the only one, by design." as *u8)
1535 return cs_foot(dst, o)
1536}
1537
1538// REGISTER: create an OPAQUE account. On success the daemon signs you in immediately and shows the recovery
1539// phrase once. The server never receives a password-equivalent at rest.
1540func cs_page_register(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
1541 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_REGISTER, "Connect — create an account" as *u8)
1542 o = cs_banner(ctx, dst, o)
1543 if ctx[CS_AUTH]==1 {
1544 // FRESH registration lands here signed-in with a one-time recovery phrase to save.
1545 if ctx[CS_MNEMONIC]!=0 {
1546 o = cu_h1(dst, o, "Welcome to Connect" as *u8, "Your account is created and you are signed in. One thing first, and it matters:" as *u8)
1547 o = cu_banner(dst, o, 2, "Save your recovery phrase now — it is shown only once" as *u8, "Write these 24 words down and keep them somewhere safe and offline. They are the ONLY way back into this account, because your password is never stored anywhere." as *u8)
1548 o = cu_puts(dst, o, "<p class=\"mono\" style=\"padding:12px;border:1px solid var(--border);border-radius:10px;background:var(--surface)\">" as *u8)
1549 o = cu_esc(dst, o, ctx[CS_MNEMONIC] as *u8)
1550 o = cu_puts(dst, o, "</p>" as *u8)
1551 o = cu_note(dst, o, "Saved it? Head to your <a href=\"/connect/account\">account</a> or start exploring <a href=\"/connect\">Connect</a>." as *u8)
1552 return cs_foot(dst, o)
1553 }
1554 o = cu_h1(dst, o, "You already have an account" as *u8, "You are signed in, so there is nothing to create." as *u8)
1555 o = cu_note(dst, o, "Manage it on your <a href=\"/connect/account\">account</a> page." as *u8)
1556 return cs_foot(dst, o)
1557 }
1558 o = cu_h1(dst, o, "Create your account" as *u8, "One handle, one passphrase. We store an OPAQUE envelope, never your password — so there is nothing offline-crackable to steal." as *u8)
1559 o = cu_form_open(dst, o, "/connect/register" as *u8)
1560 o = cu_input(dst, o, "handle" as *u8, "Handle (a–z, 0–9, _; up to 24)" as *u8, "pick a handle" as *u8, 24)
1561 o = cu_input_typed(dst, o, "pw" as *u8, "Passphrase (at least 8 characters)" as *u8, "choose a strong passphrase" as *u8, 256, "password" as *u8, "new-password" as *u8)
1562 o = cu_submit(dst, o, "Create account" as *u8)
1563 o = cu_form_close(dst, o)
1564 o = cu_note(dst, o, "Already registered? <a href=\"/connect/login\">Sign in</a>. Your account is per-Connect for now; unifying it with one family-wide sign-in is the next rung." as *u8)
1565 return cs_foot(dst, o)
1566}
1567
1568// ACCOUNT: the signed-in identity home. Sign-out is a POST here (a state change never rides a GET link).
1569func cs_page_account(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
1570 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_ACCOUNT, "Connect — your account" as *u8)
1571 o = cs_banner(ctx, dst, o)
1572 if ctx[CS_AUTH]!=1 {
1573 o = cu_h1(dst, o, "You’re not signed in" as *u8, "Sign in to see your account." as *u8)
1574 o = cu_note(dst, o, "<a href=\"/connect/login\">Sign in</a> or <a href=\"/connect/register\">create an account</a>." as *u8)
1575 return cs_foot(dst, o)
1576 }
1577 o = cu_h1(dst, o, "Your account" as *u8, "This is who you are on Connect. Your handle is your identity everywhere in the app." as *u8)
1578 // shown ONCE, right after registration: the recovery phrase is the only way back into an account whose
1579 // password we never stored. It is escaped and never persisted server-side beyond the OPAQUE record.
1580 if ctx[CS_MNEMONIC]!=0 {
1581 o = cu_banner(dst, o, 2, "Save your recovery phrase now — it is shown only once" as *u8, "Write these 24 words down and keep them somewhere safe and offline. They are the ONLY way to recover this account, because your password is not stored anywhere." as *u8)
1582 o = cu_puts(dst, o, "<p class=\"mono\" style=\"padding:12px;border:1px solid var(--border);border-radius:10px;background:var(--surface)\">" as *u8)
1583 o = cu_esc(dst, o, ctx[CS_MNEMONIC] as *u8)
1584 o = cu_puts(dst, o, "</p>" as *u8)
1585 }
1586 o = cu_rows_open(dst, o)
1587 o = cu_puts(dst, o, "<li class=\"row\"><div class=\"who\"><b>@" as *u8)
1588 if ctx[CS_HANDLE]!=0 { o = cu_esc(dst, o, ctx[CS_HANDLE] as *u8) }
1589 o = cu_puts(dst, o, "</b><span>Signed in with an OPAQUE session (the server holds no password)</span></div>" as *u8)
1590 o = cu_badge(dst, o, 1, "verified session" as *u8)
1591 o = cu_puts(dst, o, "</li>" as *u8)
1592 o = cu_rows_close(dst, o)
1593 o = cu_h2(dst, o, "Encryption identity" as *u8)
1594 o = cu_note(dst, o, "When you use <a href=\"/synth/connect_e2e\">end-to-end messaging</a> while signed in, your published key is bound to <b>@" as *u8)
1595 // (handle already escaped above; re-emit inside the note is fine, still escaped)
1596 if ctx[CS_HANDLE]!=0 { o = cu_esc(dst, o, ctx[CS_HANDLE] as *u8) }
1597 o = cu_puts(dst, o, "</b> — so no one can publish a key under your name but you." as *u8)
1598 o = cu_h2(dst, o, "Sign out" as *u8)
1599 o = cu_form_open(dst, o, "/connect/logout" as *u8)
1600 o = cu_submit(dst, o, "Sign out of this browser" as *u8)
1601 o = cu_form_close(dst, o)
1602 return cs_foot(dst, o)
1603}
1604
1605// CONTACTS: a signed-in user's own list of people they know. Each contact shows whether they have published
1606// an end-to-end key (looked up live in the shared directory via ctx[CS_SH]) -- so the list doubles as the
1607// place you start a private conversation. Adding is a set (idempotent); you cannot add yourself.
1608func cs_page_contacts(ctx: *i64, bp: *u8, dst: *u8, off: i64) -> i64 {
1609 var o: i64 = cs_shell_open(ctx, bp, dst, off, CS_P_CONTACTS, "Connect — your contacts" as *u8)
1610 o = cs_banner(ctx, dst, o)
1611 if ctx[CS_AUTH]!=1 {
1612 o = cu_h1(dst, o, "Your contacts" as *u8, "Sign in to keep a list of the people you know here." as *u8)
1613 o = cu_note(dst, o, "<a href=\"/connect/login\">Sign in</a> or <a href=\"/connect/register\">create an account</a>." as *u8)
1614 return cs_foot(dst, o)
1615 }
1616 o = cu_h1(dst, o, "Your contacts" as *u8, "The people you have added. When a contact has published an end-to-end key, you can message them privately — the server only ever sees ciphertext." as *u8)
1617 o = cu_form_open(dst, o, "/connect/addcontact" as *u8)
1618 o = cu_input(dst, o, "handle" as *u8, "Add by handle" as *u8, "their handle" as *u8, 24)
1619 o = cu_submit(dst, o, "Add contact" as *u8)
1620 o = cu_form_close(dst, o)
1621 if ctx[CS_NCONTACTS]<=0 {
1622 o = cu_empty(dst, o, "No contacts yet. Add someone by their handle above." as *u8)
1623 return cs_foot(dst, o)
1624 }
1625 o = cu_rows_open(dst, o)
1626 let sh: *i64 = ctx[CS_SH] as *i64
1627 var i: i64=0
1628 while i<ctx[CS_NCONTACTS] {
1629 let h: *u8 = cs_contact_at(ctx, i)
1630 o = cu_puts(dst, o, "<li class=\"row\">" as *u8)
1631 o = cu_avatar(dst, o, h, 0)
1632 o = cu_puts(dst, o, "<div class=\"who\"><b>@" as *u8)
1633 o = cu_esc(dst, o, h)
1634 o = cu_puts(dst, o, "</b><span>" as *u8)
1635 var haskey: i64=0
1636 if (sh as i64)!=0 { if cs_sh_pub_find(sh, h)>=0 { haskey=1 } }
1637 if haskey==1 { o = cu_puts(dst, o, "has an end-to-end key" as *u8) } else { o = cu_puts(dst, o, "no end-to-end key yet" as *u8) }
1638 o = cu_puts(dst, o, "</span></div>" as *u8)
1639 if haskey==1 { o = cu_badge(dst, o, 1, "e2e ready" as *u8) } else { o = cu_badge(dst, o, 0, "no key" as *u8) }
1640 // remove: a state change, so a POST form (never a GET link). The handle rides as a hidden field.
1641 o = cu_puts(dst, o, "<form class=\"frm\" method=\"post\" action=\"/connect/rmcontact\" style=\"margin:0\"><input type=\"hidden\" name=\"handle\" value=\"" as *u8)
1642 o = cu_esc(dst, o, h)
1643 o = cu_puts(dst, o, "\"><button class=\"go\" type=\"submit\">Remove</button></form></li>" as *u8)
1644 i=i+1
1645 }
1646 o = cu_rows_close(dst, o)
1647 o = cu_note(dst, o, "Your contact list is private to your account and travels with your sign-in. A mutual contact request (so the other person confirms) is the next rung." as *u8)
1648 return cs_foot(dst, o)
1649}
1650
1651// ---------------------------------------------------------------------------- response
1652
1653func cs_render(ctx: *i64, bp: *u8, page: i64, dst: *u8, off: i64) -> i64 {
1654 if page==CS_P_LOGIN { return cs_page_login(ctx, bp, dst, off) }
1655 if page==CS_P_REGISTER { return cs_page_register(ctx, bp, dst, off) }
1656 if page==CS_P_ACCOUNT { return cs_page_account(ctx, bp, dst, off) }
1657 if page==CS_P_CONTACTS { return cs_page_contacts(ctx, bp, dst, off) }
1658 if page==CS_P_PARTNERS { return cs_page_partners(ctx, bp, dst, off) }
1659 if page==CS_P_CHAT { return cs_page_chat(ctx, bp, dst, off) }
1660 if page==CS_P_RECON { return cs_page_reconnect(ctx, bp, dst, off) }
1661 if page==CS_P_COMMUNITY { return cs_page_community(ctx, bp, dst, off) }
1662 if page==CS_P_EVENTS { return cs_page_events(ctx, bp, dst, off) }
1663 if page==CS_P_SECURE { return cs_page_secure(ctx, bp, dst, off) }
1664 return cs_page_home(ctx, bp, dst, off)
1665}
1666
1667func cs_resp(ctx: *i64, bp: *u8, page: i64, out: *u8, cap: i64) -> i64 {
1668 let body: *u8 = sys_mmap(262144)
1669 let bn: i64 = cs_render(ctx, bp, page, body, 0)
1670 var o: i64 = cs_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " as *u8)
1671 o = cs_catn(out, o, bn)
1672 o = cs_cat(out, o, "\r\nConnection: close\r\n\r\n" as *u8)
1673 var i: i64=0
1674 while i<bn { if o+i<cap { out[o+i]=body[i] } i=i+1 }
1675 return o+bn
1676}
1677
1678// map the (already prefix-stripped) path to a page code.
1679func cs_route(p: *u8) -> i64 {
1680 if cs_seq(p, "/partners" as *u8)==1 { return CS_P_PARTNERS }
1681 if cs_seq(p, "/chat" as *u8)==1 { return CS_P_CHAT }
1682 if cs_seq(p, "/say" as *u8)==1 { return CS_P_CHAT }
1683 if cs_seq(p, "/correct" as *u8)==1 { return CS_P_CHAT }
1684 if cs_seq(p, "/lane" as *u8)==1 { return CS_P_CHAT }
1685 if cs_seq(p, "/block" as *u8)==1 { return CS_P_CHAT }
1686 if cs_seq(p, "/reconnect" as *u8)==1 { return CS_P_RECON }
1687 if cs_seq(p, "/consent" as *u8)==1 { return CS_P_RECON }
1688 if cs_seq(p, "/find" as *u8)==1 { return CS_P_RECON }
1689 if cs_seq(p, "/community" as *u8)==1 { return CS_P_COMMUNITY }
1690 if cs_seq(p, "/vote" as *u8)==1 { return CS_P_COMMUNITY }
1691 if cs_seq(p, "/join" as *u8)==1 { return CS_P_COMMUNITY }
1692 if cs_seq(p, "/faith" as *u8)==1 { return CS_P_COMMUNITY }
1693 if cs_seq(p, "/events" as *u8)==1 { return CS_P_EVENTS }
1694 if cs_seq(p, "/rsvp" as *u8)==1 { return CS_P_EVENTS }
1695 if cs_seq(p, "/secure" as *u8)==1 { return CS_P_SECURE }
1696 if cs_seq(p, "/seal" as *u8)==1 { return CS_P_SECURE }
1697 if cs_seq(p, "/login" as *u8)==1 { return CS_P_LOGIN }
1698 if cs_seq(p, "/register" as *u8)==1 { return CS_P_REGISTER }
1699 if cs_seq(p, "/account" as *u8)==1 { return CS_P_ACCOUNT }
1700 if cs_seq(p, "/logout" as *u8)==1 { return CS_P_ACCOUNT }
1701 if cs_seq(p, "/contacts" as *u8)==1 { return CS_P_CONTACTS }
1702 if cs_seq(p, "/addcontact" as *u8)==1 { return CS_P_CONTACTS }
1703 if cs_seq(p, "/rmcontact" as *u8)==1 { return CS_P_CONTACTS }
1704 return CS_P_HOME
1705}
1706
1707// request -> response. base prefix stripped when present (edge mount + direct port both work).
1708// request -> response over BOTH planes: ctx = this session's private world, sh = the daemon's shared
1709// content-blind plane (0 = plane absent -> the shared routes are simply not there; cs_handle keeps the
1710// original 5-arg contract for every existing gate/caller).
1711func cs_handle2(ctx: *i64, sh: *i64, req: *u8, reqlen: i64, out: *u8, cap: i64) -> i64 {
1712 let bp: *u8 = "/connect" as *u8
1713 var is_post: i64=0
1714 if cs_starts(req, "POST " as *u8)==1 { is_post=1 }
1715 // path token
1716 let path: *u8 = sys_mmap(300)
1717 // advance just past the first space (end of the method token)
1718 var i: i64=0
1719 var seen: i64=0
1720 while seen==0 {
1721 if i>=reqlen { seen=1 } else {
1722 if req[i]==(32 as u8) { i=i+1; seen=1 } else { i=i+1 }
1723 }
1724 }
1725 // collect the path up to the next space
1726 var pl: i64=0
1727 var pdone: i64=0
1728 while pdone==0 {
1729 if i>=reqlen { pdone=1 } else {
1730 if req[i]==(32 as u8) { pdone=1 } else {
1731 if pl<299 { path[pl]=req[i]; pl=pl+1 }
1732 i=i+1
1733 }
1734 }
1735 }
1736 path[pl]=0 as u8
1737 // split the query string: PATH?QUERY -> z-term the path at '?', q -> the query (k=v&k=v form)
1738 var qpos: i64 = 0-1
1739 var qq: i64=0
1740 while path[qq]!=(0 as u8) {
1741 if qpos<0 { if path[qq]==(63 as u8) { qpos=qq } }
1742 qq=qq+1
1743 }
1744 var q: *u8 = (path as i64 + pl) as *u8
1745 var qlen: i64 = 0
1746 if qpos>=0 {
1747 path[qpos]=0 as u8
1748 q=(path as i64 + qpos+1) as *u8
1749 qlen=pl-qpos-1
1750 }
1751 var p: *u8 = path
1752 if cs_starts(p, bp)==1 { p = (path as i64 + cs_slen(bp)) as *u8 }
1753 if p[0]==(0 as u8) { p = "/" as *u8 }
1754 // body = after CRLFCRLF
1755 var boff: i64 = reqlen
1756 var j: i64=0
1757 while j+3<reqlen {
1758 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) { boff=j+4; j=reqlen } } } }
1759 j=j+1
1760 }
1761 let body: *u8 = (req as i64 + boff) as *u8
1762 let blen: i64 = reqlen - boff
1763 ctx[CS_BANNER]=CS_B_NONE
1764
1765 // ---- identity JSON: who is signed in on this browser session? (works with or without a shared plane)
1766 // The device messenger calls this to show the signed-in handle and to know whether the key it publishes
1767 // will be VERIFIED (bound to an account) or unverified. No secret is exposed -- handle + a boolean.
1768 if cs_seq(p, "/me" as *u8)==1 {
1769 let jm: *u8 = sys_mmap(512)
1770 var jmn: i64 = cs_cat(jm, 0, "{\"v\":1,\"authed\":" as *u8)
1771 jmn = cs_catn(jm, jmn, ctx[CS_AUTH])
1772 jmn = cs_cat(jm, jmn, ",\"handle\":\"" as *u8)
1773 if ctx[CS_AUTH]==1 { if ctx[CS_HANDLE]!=0 { jmn = cs_cat(jm, jmn, ctx[CS_HANDLE] as *u8) } }
1774 jmn = cs_cat(jm, jmn, "\"}" as *u8)
1775 return cs_json_resp(jm, jmn, out, cap)
1776 }
1777
1778 // ---- shared content-blind plane routes (JSON; live only when the daemon passed a plane) ----
1779 if (sh as i64)!=0 {
1780 if cs_seq(p, "/pubkey" as *u8)==1 { if is_post==1 {
1781 let nv: *u8 = sys_mmap(64)
1782 let pv: *u8 = sys_mmap(128)
1783 cs_form_get(body, blen, "n" as *u8, nv, 63)
1784 cs_form_get(body, blen, "p" as *u8, pv, 127)
1785 // the daemon stamps the authenticated 32-byte user-id into ctx[CS_UID] (0 when anonymous).
1786 // an authenticated publish BINDS + VERIFIES the key; an anonymous one stays unverified (legacy)
1787 // and can never overwrite a verified handle -- that is the anti-impostor guarantee.
1788 let owner_ptr: i64 = ctx[CS_UID]
1789 var use_auth: i64 = 0
1790 if ctx[CS_AUTH]==1 { if owner_ptr!=0 { use_auth=1 } }
1791 var oarg: *u8 = nv
1792 if owner_ptr!=0 { oarg = owner_ptr as *u8 }
1793 // ANTI-SQUAT: a signed-in caller may publish ONLY under their own account handle. Without this a
1794 // verified account could claim -- and permanently LOCK -- somebody else's name (the mirror-image
1795 // of the impostor hole). -5 = the requested name is not your account's handle.
1796 var mism: i64 = 0
1797 if use_auth==1 { if ctx[CS_HANDLE]!=0 { if cs_seq(nv, ctx[CS_HANDLE] as *u8)==0 { mism=1 } } }
1798 var r: i64 = 0-5
1799 if mism==0 { r = cs_sh_pub_put2(sh, nv, pv, oarg, use_auth) }
1800 let jb: *u8 = sys_mmap(256)
1801 var jn: i64 = cs_cat(jb, 0, "{\"v\":1,\"ok\":" as *u8)
1802 var okv: i64=0
1803 if r==1 { okv=1 }
1804 if r==2 { okv=1 }
1805 jn = cs_catn(jb, jn, okv)
1806 jn = cs_cat(jb, jn, ",\"verified\":" as *u8)
1807 jn = cs_catn(jb, jn, use_auth)
1808 jn = cs_cat(jb, jn, ",\"r\":" as *u8)
1809 jn = cs_catsn(jb, jn, r)
1810 jn = cs_cat(jb, jn, "}" as *u8)
1811 return cs_json_resp(jb, jn, out, cap)
1812 } }
1813 if cs_seq(p, "/pubkeys" as *u8)==1 {
1814 let jb2: *u8 = sys_mmap(16384)
1815 var jn2: i64 = cs_cat(jb2, 0, "{\"v\":1,\"keys\":[" as *u8)
1816 var ki: i64=0
1817 while ki<sh[0] {
1818 if ki>0 { jn2 = cs_cat(jb2, jn2, "," as *u8) }
1819 jn2 = cs_cat(jb2, jn2, "{\"n\":\"" as *u8)
1820 jn2 = cs_cat(jb2, jn2, sh_pub_at(sh,ki))
1821 jn2 = cs_cat(jb2, jn2, "\",\"p\":\"" as *u8)
1822 jn2 = cs_cat(jb2, jn2, (sh_pub_at(sh,ki) as i64 + SH_NAME) as *u8)
1823 let owk: *u8 = sh_own_at(sh, ki)
1824 jn2 = cs_cat(jb2, jn2, "\",\"verified\":" as *u8)
1825 jn2 = cs_catn(jb2, jn2, owk[32] as i64)
1826 jn2 = cs_cat(jb2, jn2, "}" as *u8)
1827 ki=ki+1
1828 }
1829 jn2 = cs_cat(jb2, jn2, "]}" as *u8)
1830 return cs_json_resp(jb2, jn2, out, cap)
1831 }
1832 if cs_seq(p, "/sealed" as *u8)==1 {
1833 let uv: *u8 = sys_mmap(64)
1834 cs_form_get(q, qlen, "u" as *u8, uv, 63)
1835 let jb3: *u8 = sys_mmap(65536)
1836 var jn3: i64 = cs_cat(jb3, 0, "{\"v\":1,\"u\":\"" as *u8)
1837 if cs_name_ok(uv)==1 { jn3 = cs_cat(jb3, jn3, uv) }
1838 jn3 = cs_cat(jb3, jn3, "\",\"msgs\":[" as *u8)
1839 var nm: i64=0
1840 var mi: i64=0
1841 while mi<sh[1] {
1842 let row: *u8 = sh_msg_at(sh, mi)
1843 let dfm: *u8 = sh_del_at(sh, mi)
1844 // delivered (tombstoned) messages are NEVER served -- the device that acked holds the copy.
1845 if cs_seq((row as i64 + SH_NAME) as *u8, uv)==1 { if (dfm[0] as i64)==0 {
1846 if nm>0 { jn3 = cs_cat(jb3, jn3, "," as *u8) }
1847 jn3 = cs_cat(jb3, jn3, "{\"f\":\"" as *u8)
1848 jn3 = cs_cat(jb3, jn3, row)
1849 jn3 = cs_cat(jb3, jn3, "\",\"p\":\"" as *u8)
1850 let sp2: i64 = cs_sh_pub_find(sh, row)
1851 if sp2>=0 { jn3 = cs_cat(jb3, jn3, (sh_pub_at(sh,sp2) as i64 + SH_NAME) as *u8) }
1852 jn3 = cs_cat(jb3, jn3, "\",\"s\":" as *u8)
1853 jn3 = cs_cat(jb3, jn3, (row as i64 + SH_NAME + SH_NAME) as *u8)
1854 jn3 = cs_cat(jb3, jn3, ",\"ct\":\"" as *u8)
1855 jn3 = cs_cat(jb3, jn3, (row as i64 + SH_NAME + SH_NAME + SH_SEQ) as *u8)
1856 jn3 = cs_cat(jb3, jn3, "\"}" as *u8)
1857 nm=nm+1
1858 } }
1859 mi=mi+1
1860 }
1861 jn3 = cs_cat(jb3, jn3, "]}" as *u8)
1862 return cs_json_resp(jb3, jn3, out, cap)
1863 }
1864 // DELETE-ON-DELIVERY: the recipient's device confirms it stored the message in its own vault ->
1865 // tombstone it so our server never serves it again (nothing durable retained server-side).
1866 if cs_seq(p, "/ack" as *u8)==1 { if is_post==1 {
1867 let af: *u8 = sys_mmap(64)
1868 let at: *u8 = sys_mmap(64)
1869 let asq: *u8 = sys_mmap(64)
1870 cs_form_get(body, blen, "from" as *u8, af, 63)
1871 cs_form_get(body, blen, "to" as *u8, at, 63)
1872 cs_form_get(body, blen, "seq" as *u8, asq, 63)
1873 let r: i64 = cs_sh_msg_ack(sh, af, at, asq)
1874 let jb5: *u8 = sys_mmap(128)
1875 var jn5: i64 = cs_cat(jb5, 0, "{\"v\":1,\"ok\":" as *u8)
1876 jn5 = cs_catn(jb5, jn5, r)
1877 jn5 = cs_cat(jb5, jn5, "}" as *u8)
1878 return cs_json_resp(jb5, jn5, out, cap)
1879 } }
1880 }
1881
1882 if is_post==1 {
1883 let v: *u8 = sys_mmap(256)
1884 if cs_seq(p, "/say" as *u8)==1 {
1885 let tn: i64 = cs_form_get(body, blen, "text" as *u8, v, 120)
1886 let sp: *u8 = sys_mmap(32)
1887 cs_form_get(body, blen, "speaker" as *u8, sp, 31)
1888 if tn>0 { if ctx[CS_NMSG]<CS_MSGCAP {
1889 let k: i64 = ctx[CS_NMSG]
1890 let spk: *i64 = ctx[CS_SPK] as *i64
1891 let txt: *u8 = ctx[CS_TXTA] as *u8
1892 if cs_seq(sp, "vera" as *u8)==1 { spk[k]=1 } else { spk[k]=0 }
1893 var w: i64=0
1894 while v[w]!=(0 as u8) { txt[k*CS_TXT+w]=v[w]; w=w+1 }
1895 txt[k*CS_TXT+w]=0 as u8
1896 ctx[CS_NMSG]=k+1
1897 } }
1898 }
1899 if cs_seq(p, "/correct" as *u8)==1 {
1900 let tn: i64 = cs_form_get(body, blen, "text" as *u8, v, 120)
1901 if tn>0 { if ctx[CS_NMSG]>0 {
1902 let k: i64 = ctx[CS_NMSG]-1
1903 let corr: *u8 = ctx[CS_CORR] as *u8
1904 var w: i64=0
1905 while v[w]!=(0 as u8) { corr[k*CS_TXT+w]=v[w]; w=w+1 }
1906 corr[k*CS_TXT+w]=0 as u8
1907 } }
1908 }
1909 if cs_seq(p, "/rsvp" as *u8)==1 {
1910 let who: *u8 = sys_mmap(32)
1911 let ev: *u8 = sys_mmap(32)
1912 cs_form_get(body, blen, "who" as *u8, who, 31)
1913 cs_form_get(body, blen, "ev" as *u8, ev, 31)
1914 // Admission is decided by the gated engine, not by this page. ev_rsvp enforces membership,
1915 // idempotency, the categorical youth wall and capacity IN THAT ORDER, so a nearly-full event
1916 // can never become the reason a safety rule is skipped.
1917 let ew: *i64 = cs_ev_world(ctx)
1918 var evx: i64 = CS_EV_YOUTH
1919 if cs_seq(ev, "picnic" as *u8)==1 { evx = CS_EV_PICNIC }
1920 var actor: i64 = CS_EV_NEW_ADULT
1921 var actor_minor: i64 = 0
1922 if cs_seq(who, "minor" as *u8)==1 { actor = CS_EV_NEW_MINOR; actor_minor = 1 }
1923 let rc: i64 = ev_rsvp(ew, evx, actor)
1924 if rc==1 {
1925 if evx==CS_EV_PICNIC { ctx[CS_EV1_GOING]=ctx[CS_EV1_GOING]+1 }
1926 else {
1927 if actor_minor==1 { ctx[CS_EV2_MINORS]=ctx[CS_EV2_MINORS]+1 }
1928 else { ctx[CS_EV2_ADULTS]=ctx[CS_EV2_ADULTS]+1 }
1929 }
1930 ctx[CS_BANNER]=CS_B_RSVP_OK
1931 }
1932 if rc==(0-1) { ctx[CS_BANNER]=CS_B_RSVP_NOTMEMBER }
1933 if rc==(0-2) { ctx[CS_BANNER]=CS_B_RSVP_FULL }
1934 if rc==(0-3) { ctx[CS_BANNER]=CS_B_RSVP_NO }
1935 if rc==(0-4) { ctx[CS_BANNER]=CS_B_RSVP_ADULTONLY }
1936 }
1937 if cs_seq(p, "/vote" as *u8)==1 {
1938 let ov: *u8 = sys_mmap(16)
1939 cs_form_get(body, blen, "opt" as *u8, ov, 15)
1940 var pick: i64=0
1941 if ov[0]==(49 as u8) { pick=1 }
1942 if ov[0]==(50 as u8) { pick=2 }
1943 if ov[0]==(51 as u8) { pick=3 }
1944 if pick>0 { ctx[CS_POLL]=pick; ctx[CS_BANNER]=CS_B_VOTE_OK }
1945 }
1946 if cs_seq(p, "/consent" as *u8)==1 {
1947 let sv: *u8 = sys_mmap(32)
1948 cs_form_get(body, blen, "set" as *u8, sv, 31)
1949 if cs_seq(sv, "on" as *u8)==1 { ctx[CS_RC_CONSENT]=1; ctx[CS_BANNER]=CS_B_CONSENT_ON }
1950 else { ctx[CS_RC_CONSENT]=0; ctx[CS_RC_DONE]=0; ctx[CS_BANNER]=CS_B_CONSENT_OFF }
1951 }
1952 if cs_seq(p, "/find" as *u8)==1 {
1953 let mv: *u8 = sys_mmap(32)
1954 cs_form_get(body, blen, "mission" as *u8, mv, 31)
1955 ctx[CS_RC_MISSION]=cs_atoi(mv, ctx[CS_RC_MISSION])
1956 cs_form_get(body, blen, "area" as *u8, mv, 31)
1957 ctx[CS_RC_AREA]=cs_atoi(mv, ctx[CS_RC_AREA])
1958 cs_form_get(body, blen, "from" as *u8, mv, 31)
1959 ctx[CS_RC_FROM]=cs_atoi(mv, ctx[CS_RC_FROM])
1960 cs_form_get(body, blen, "to" as *u8, mv, 31)
1961 ctx[CS_RC_TO]=cs_atoi(mv, ctx[CS_RC_TO])
1962 ctx[CS_RC_DONE]=1
1963 ctx[CS_BANNER]=CS_B_SEARCHED
1964 }
1965 if cs_seq(p, "/lane" as *u8)==1 {
1966 let lv: *u8 = sys_mmap(32)
1967 cs_form_get(body, blen, "mine" as *u8, lv, 31)
1968 ctx[CS_LANE_ME]=cs_atoi(lv, ctx[CS_LANE_ME])
1969 cs_form_get(body, blen, "theirs" as *u8, lv, 31)
1970 ctx[CS_LANE_THEM]=cs_atoi(lv, ctx[CS_LANE_THEM])
1971 ctx[CS_BANNER]=CS_B_LANE
1972 }
1973 if cs_seq(p, "/block" as *u8)==1 {
1974 let bv: *u8 = sys_mmap(32)
1975 cs_form_get(body, blen, "set" as *u8, bv, 31)
1976 if cs_seq(bv, "on" as *u8)==1 { ctx[CS_BLOCKED]=1; ctx[CS_BANNER]=CS_B_BLOCK }
1977 else { ctx[CS_BLOCKED]=0; ctx[CS_BANNER]=CS_B_UNBLOCK }
1978 }
1979 if cs_seq(p, "/faith" as *u8)==1 {
1980 let fv: *u8 = sys_mmap(32)
1981 cs_form_get(body, blen, "set" as *u8, fv, 31)
1982 if cs_seq(fv, "on" as *u8)==1 { ctx[CS_FAITH_CONSENT]=1; ctx[CS_BANNER]=CS_B_FAITH_ON }
1983 else {
1984 // withdrawing consent also removes the membership it authorised -- consent is not a
1985 // one-way door, and a roster entry may not outlive the permission that created it.
1986 ctx[CS_FAITH_CONSENT]=0
1987 ctx[CS_WARD]=0
1988 ctx[CS_BANNER]=CS_B_FAITH_OFF
1989 }
1990 }
1991 // CONTACTS (signed-in only): a per-account, identity-keyed list persisted with the world.
1992 if cs_seq(p, "/addcontact" as *u8)==1 { if ctx[CS_AUTH]==1 {
1993 let cn: *u8 = sys_mmap(40)
1994 cs_form_get(body, blen, "handle" as *u8, cn, 39)
1995 cs_lower_ascii(cn)
1996 let r: i64 = cs_contact_add(ctx, cn)
1997 if r==1 { ctx[CS_BANNER]=CS_B_CONTACT_ADDED }
1998 if r==0 { ctx[CS_BANNER]=CS_B_CONTACT_BAD }
1999 if r==(0-1) { ctx[CS_BANNER]=CS_B_CONTACT_DUP }
2000 if r==(0-2) { ctx[CS_BANNER]=CS_B_CONTACT_FULL }
2001 if r==(0-3) { ctx[CS_BANNER]=CS_B_CONTACT_SELF }
2002 } }
2003 if cs_seq(p, "/rmcontact" as *u8)==1 { if ctx[CS_AUTH]==1 {
2004 let cn2: *u8 = sys_mmap(40)
2005 cs_form_get(body, blen, "handle" as *u8, cn2, 39)
2006 cs_lower_ascii(cn2)
2007 if cs_contact_remove(ctx, cn2)==1 { ctx[CS_BANNER]=CS_B_CONTACT_RM }
2008 } }
2009 if cs_seq(p, "/join" as *u8)==1 {
2010 // the join runs through the SAME consent-gated, idempotent function the gate verifies
2011 let mw: *i64 = sys_mmap(WM_MAX*8) as *i64
2012 let mu: *i64 = sys_mmap(WM_MAX*8) as *i64
2013 let nmc: *i64 = sys_mmap(8) as *i64
2014 nmc[0]=0
2015 if ctx[CS_WARD]==1 { wd_join(mw, mu, nmc, 71, 1, 1) } // replay this session's prior join
2016 let r: i64 = wd_join(mw, mu, nmc, 71, 1, ctx[CS_FAITH_CONSENT])
2017 if r==WD_JOINED { ctx[CS_WARD]=1; ctx[CS_BANNER]=CS_B_JOINED }
2018 if r==WD_ALREADY { ctx[CS_BANNER]=CS_B_ALREADY }
2019 if r==WD_NO_CONSENT { ctx[CS_BANNER]=CS_B_JOIN_NO }
2020 }
2021 if cs_seq(p, "/seal" as *u8)==1 {
2022 // CONTENT-BLIND RECEIVE: accept the browser encryptor's ciphertext hex and store it AS-IS.
2023 // The server has NO key -- it never decrypts. It validates the payload is hex (never plaintext)
2024 // and appends it to the content-blind log. This is the server half of the E2E loop.
2025 let cv: *u8 = sys_mmap(CS_CTHEX+8)
2026 let cn: i64 = cs_form_get(body, blen, "ct" as *u8, cv, CS_CTHEX-1)
2027 var ishex: i64=1
2028 if cn<=0 { ishex=0 }
2029 var h: i64=0
2030 while h<cn {
2031 let c: i64 = cv[h] as i64
2032 var ok: i64=0
2033 if c>=48 { if c<=57 { ok=1 } }
2034 if c>=97 { if c<=102 { ok=1 } }
2035 if c>=65 { if c<=70 { ok=1 } }
2036 if ok==0 { ishex=0; h=cn }
2037 h=h+1
2038 }
2039 // ROUTED delivery (two-user E2E): when from/to are present AND the shared plane exists, the
2040 // ciphertext crosses sessions -- stored under the recipient's name with the sender's name and
2041 // the per-message nonce seq, so the recipient's browser can derive the same X25519 shared key
2042 // and decrypt. Reply is JSON (the wasm client speaks fetch, not the HTML shell). The legacy
2043 // per-session path below is untouched: no from/to -> exactly the old behavior.
2044 if (sh as i64)!=0 {
2045 let fv: *u8 = sys_mmap(64)
2046 let tv: *u8 = sys_mmap(64)
2047 let sv: *u8 = sys_mmap(64)
2048 cs_form_get(body, blen, "from" as *u8, fv, 63)
2049 cs_form_get(body, blen, "to" as *u8, tv, 63)
2050 cs_form_get(body, blen, "seq" as *u8, sv, 63)
2051 if fv[0]!=(0 as u8) { if tv[0]!=(0 as u8) {
2052 var mr: i64 = 0-2
2053 if ishex==1 { mr = cs_sh_msg_add(sh, fv, tv, sv, cv) }
2054 let jb4: *u8 = sys_mmap(256)
2055 var jn4: i64 = cs_cat(jb4, 0, "{\"v\":1,\"ok\":" as *u8)
2056 var okm: i64=0
2057 if mr>=0 { okm=1 }
2058 jn4 = cs_catn(jb4, jn4, okm)
2059 jn4 = cs_cat(jb4, jn4, ",\"r\":" as *u8)
2060 jn4 = cs_catsn(jb4, jn4, mr)
2061 jn4 = cs_cat(jb4, jn4, "}" as *u8)
2062 return cs_json_resp(jb4, jn4, out, cap)
2063 } }
2064 }
2065 if ishex==1 { if (cn/2)*2==cn { if ctx[CS_NCT]<CS_MSGCAP {
2066 let ctl: *u8 = ctx[CS_CT] as *u8
2067 let k: i64 = ctx[CS_NCT]
2068 var w: i64=0
2069 while w<cn { ctl[k*CS_CTHEX+w]=cv[w]; w=w+1 }
2070 ctl[k*CS_CTHEX+cn]=0 as u8
2071 ctx[CS_NCT]=k+1
2072 ctx[CS_BANNER]=CS_B_SEALED
2073 } } } else { ctx[CS_BANNER]=CS_B_SEAL_BAD }
2074 }
2075 }
2076 return cs_resp(ctx, bp, cs_route(p), out, cap)
2077}
2078
2079// the original 5-arg contract, kept verbatim for every existing gate/caller: no shared plane.
2080func cs_handle(ctx: *i64, req: *u8, reqlen: i64, out: *u8, cap: i64) -> i64 {
2081 return cs_handle2(ctx, 0 as *i64, req, reqlen, out, cap)
2082}