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 &mdash; 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 &mdash; 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 &mdash; nobody is listed by default." as *u8) } 811 if b==CS_B_JOIN_NO { o = cu_banner(dst, o, 2, "Join refused &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; it holds no key and cannot decrypt it." as *u8) } 822 if b==CS_B_SEAL_BAD { o = cu_banner(dst, o, 2, "Rejected &mdash; 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 &mdash; 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 &mdash; and you&rsquo;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 &mdash; 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&ndash;24 characters of a&ndash;z, 0&ndash;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 &mdash; 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&ndash;24 characters of a&ndash;z, 0&ndash;9 or underscore." as *u8) } 834 if b==CS_B_CONTACT_SELF { o = cu_banner(dst, o, 2, "That&rsquo;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 &amp; honest by construction: no adult&ndash;youth private messages &middot; zero paywalled connection features &middot; content-blind server &middot; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &amp; branch community</h3><p class=\"muted\">A place to belong between Sundays. Community only &mdash; 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 &mdash; 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 &middot; learning English &middot; shares your taste in music" as *u8 } 890 if i==1 { return "Teaches Russian &middot; learning English &middot; shares music and cooking" as *u8 } 891 if i==2 { return "Teaches Russian &middot; learning <b>French</b> &mdash; and you cannot teach French" as *u8 } 892 return "Teaches Russian &middot; learning English &middot; <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 &mdash; 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 &mdash; 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 &ldquo;low&rdquo;. 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 &mdash; 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> &mdash; 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 &mdash; 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 &mdash; 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 привет друг, спасибо, любовь&hellip;" 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 &mdash; 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 &mdash; a relationship sits at the level you have <i>both</i> opted into, so nobody can be escalated toward romance by someone else&rsquo;s wishing. Blocking is the deliberate opposite: it takes effect instantly and needs nobody&rsquo;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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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&rsquo;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, "&ndash;" 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 &middot; Fisherton &middot; 2019&ndash;2021 &middot; 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 &middot; Centro and Fisherton &middot; 2018&ndash;2020" as *u8, p1, "" as *u8) 1182 o = cs_recon_person(dst, o, me, "Brother Alvarez" as *u8, "Argentina Rosario &middot; Funes &middot; 2019&ndash;2021" as *u8, p3, "Same mission and the same years, but a different area &mdash; you were never in the same place." as *u8) 1183 o = cs_recon_person(dst, o, me, "Elder Whitfield" as *u8, "Argentina Rosario &middot; Fisherton &middot; 2024&ndash;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 &middot; 2019&ndash;2021" as *u8, p5, "A different mission." as *u8) 1185 o = cs_recon_person(dst, o, me, "Brother Reyes" as *u8, "Argentina Rosario &middot; Fisherton &middot; 2019&ndash;2021" as *u8, p6, "You did overlap &mdash; 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 &ldquo;same mission&rdquo; 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 &mdash; 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 &mdash; 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 &mdash; community" as *u8) 1194 o = cs_banner(ctx, dst, o) 1195 o = cu_h1(dst, o, "Ward &amp; branch community" as *u8, "A place to belong between Sundays &mdash; 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 &mdash; 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&rsquo;s roster.</p>" as *u8) 1226 } else { 1227 o = cu_puts(dst, o, "<p class=\"muted\">Open to anyone who feels at home here &mdash; 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> &mdash; 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> &mdash; 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 &mdash; 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 &mdash; 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&rsquo;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 &mdash; 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 &mdash; 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 &mdash; 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 &middot; Saturday afternoon &middot; " 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 &middot; requires two adult leaders present &middot; " 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 &mdash; youth may join" as *u8) 1447 } else { 1448 o = cu_badge(dst, o, 2, "two-deep required &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; the wall is narrow on purpose, because a safety rule that gets in everyone&rsquo;s way is one people route around. Only the last three rows are constrained, and the final one &mdash; a private adult-to-youth message &mdash; 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 &mdash; 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&rsquo;s browser. The server stored it as-is &mdash; it holds no key, so this is all it can ever see." as *u8) 1493 o = cu_note(dst, o, "Compose &amp; encrypt in the browser at <a href=\"/synth/connect_e2e\">/synth/connect_e2e</a> &mdash; 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&rsquo;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 &mdash; sign in" as *u8) 1522 o = cs_banner(ctx, dst, o) 1523 if ctx[CS_AUTH]==1 { 1524 o = cu_h1(dst, o, "You&rsquo;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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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&ndash;z, 0&ndash;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 &mdash; your account" as *u8) 1571 o = cs_banner(ctx, dst, o) 1572 if ctx[CS_AUTH]!=1 { 1573 o = cu_h1(dst, o, "You&rsquo;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 &mdash; 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> &mdash; 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 &mdash; 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 &mdash; 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}