code wiki / _hdl_build / nx_connect_api_lib.nx
nx_connect_api_lib.nx source
↩ module page · 368 lines · 20182 B
1// nx_connect_api_lib.nx -- the CONNECT FRONT DOOR: one stateless verb dispatcher over all 8 capability
2// engines, emitting versioned JSON. This is what makes CONNECT MCP-ready, API-ready, agent-ready,
3// workflow-ready, and horizontally scalable in ONE surface:
4//
5// * OBJECT-ORIENTED: the 8 `nx_connect_*_lib.nx` organs ARE the objects -- each encapsulates one
6// capability's state contract and operations (single responsibility). THIS file is the facade/registry
7// over them; it owns no capability logic, only routing + serialization.
8// * MCP + API: a thin CLI (nx_connect_api.nx) wraps ca_dispatch and is registered via
9// POST /api/tools/register, so it appears as mcp__nishi__nx_connect and is drivable over the mgmt API.
10// * AGENTIC + WORKFLOW: the `help` verb returns a machine-readable catalog of every verb and its argument
11// shape, so an agent or a Workflow can DISCOVER the interface at runtime instead of being told it.
12// * SCALE: every verb is a PURE function of its arguments -- no shared mutable state, no session, no disk.
13// Any number of calls run independently and in parallel; the tool can be replicated with zero coordination.
14// * CONTRACT STABILITY (rule 19): every response carries {"v":1,...}; new fields are additive.
15// * STRUCTURED ERRORS (rule 6): a bad call returns {"ok":0,"error":<slug>,"detail":<human>}, never a crash.
16//
17// PURE CORE: no main (so the gate imports it and drives ca_dispatch with synthetic argv). license_tier: ORIGINAL
18import "nx_connect_match_lib.nx"
19import "nx_connect_lds_reconnect_lib.nx"
20import "nx_connect_youth_lib.nx"
21import "nx_connect_wards_lib.nx"
22import "nx_connect_lanes_lib.nx"
23import "nx_connect_events_lib.nx"
24import "nx_connect_polls_lib.nx"
25import "nx_connect_group_lib.nx"
26import "nx_connect_graph_lib.nx"
27import "nx_mt_multi.nx"
28const CA_MAGIC_4096: i64 = 4096
29const CA_MAGIC_4095: i64 = 4095
30
31const CA_VER: i64 = 1
32const CA_LANG_MAX: i64 = 4 // MM_EN=0 MM_RU=1 MM_BE=2 MM_UK=3 MM_PL=4
33
34// ---- serialization + arg helpers (self-contained; JSON, not HTML) ----
35func ca_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
36func ca_puts(o: *u8, off: i64, s: *u8) -> i64 { var p: i64=off; var i: i64=0; while s[i]!=(0 as u8){o[p]=s[i]; p=p+1; i=i+1} return p }
37// base-10 int, allocation-free, handles negatives (excluded sentinels are -1)
38func ca_putn(o: *u8, off: i64, v: i64) -> i64 {
39 var p: i64=off
40 var x: i64=v
41 if x<0 { o[p]=45 as u8; p=p+1; x=0-x }
42 if x==0 { o[p]=48 as u8; return p+1 }
43 var d: i64=1
44 var m: i64=x
45 while m>=10 { d=d*10; m=m/10 }
46 while d>0 { let q: i64=x/d; o[p]=(48+q) as u8; p=p+1; x=x-(q*d); d=d/10 }
47 return p
48}
49func ca_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]!=(0 as u8){return 0} return 1 }
50// parse a (possibly negative) base-10 int from a NUL-terminated arg
51func ca_atoi(s: *u8) -> i64 {
52 var i: i64=0
53 var neg: i64=0
54 if s[0]==(45 as u8) { neg=1; i=1 }
55 var v: i64=0
56 while s[i]!=(0 as u8) { let c: i64=s[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 }
57 if neg==1 { return 0-v }
58 return v
59}
60// i-th positional arg as an int (0 if absent) -- argv[0] is the verb, args start at 1
61func ca_ia(argv: *i64, argc: i64, i: i64) -> i64 { if i>=argc { return 0 } return ca_atoi(argv[i] as *u8) }
62
63// JSON envelope: {"v":1,"tool":"nx_connect","verb":"<v>", (caller closes)
64func ca_open(o: *u8, off: i64, verb: *u8) -> i64 {
65 var p: i64 = ca_puts(o, off, "{\"v\":" as *u8)
66 p = ca_putn(o, p, CA_VER)
67 p = ca_puts(o, p, ",\"tool\":\"nx_connect\",\"verb\":\"" as *u8)
68 p = ca_puts(o, p, verb)
69 p = ca_puts(o, p, "\"," as *u8)
70 return p
71}
72// structured error close
73func ca_err(o: *u8, off: i64, verb: *u8, slug: *u8, detail: *u8) -> i64 {
74 var p: i64 = ca_open(o, off, verb)
75 p = ca_puts(o, p, "\"ok\":0,\"error\":\"" as *u8)
76 p = ca_puts(o, p, slug)
77 p = ca_puts(o, p, "\",\"detail\":\"" as *u8)
78 p = ca_puts(o, p, detail)
79 p = ca_puts(o, p, "\"}" as *u8)
80 return p
81}
82// escape a string for a JSON value: backslash and double-quote get escaped, control bytes (<0x20) become
83// spaces, everything else -- including raw UTF-8 continuation bytes (Cyrillic etc.) -- passes through, which
84// is valid JSON. This is the JSON analogue of the app's HTML escaper; the front door must never emit a
85// string that breaks its own contract.
86func ca_esc_json(o: *u8, off: i64, s: *u8) -> i64 {
87 var p: i64=off
88 var i: i64=0
89 while s[i]!=(0 as u8) {
90 let c: i64 = s[i] as i64
91 if c==92 { o[p]=92 as u8; o[p+1]=92 as u8; p=p+2 } else {
92 if c==34 { o[p]=92 as u8; o[p+1]=34 as u8; p=p+2 } else {
93 if c>=0 { if c<32 { o[p]=32 as u8; p=p+1 } else { o[p]=s[i]; p=p+1 } } else { o[p]=s[i]; p=p+1 } } }
94 i=i+1
95 }
96 return p
97}
98
99// one {"key":int} pair
100func ca_kv(o: *u8, off: i64, key: *u8, v: i64) -> i64 {
101 var p: i64 = ca_puts(o, off, "\"" as *u8)
102 p = ca_puts(o, p, key)
103 p = ca_puts(o, p, "\":" as *u8)
104 p = ca_putn(o, p, v)
105 return p
106}
107
108// ---- the self-describing catalog (agentic/workflow discovery) ----
109func ca_help(o: *u8, off: i64) -> i64 {
110 var p: i64 = ca_open(o, off, "help" as *u8)
111 p = ca_puts(o, p, "\"ok\":1,\"stateless\":1,\"verbs\":[" as *u8)
112 p = ca_puts(o, p, "{\"name\":\"match\",\"args\":\"a_age a_learn a_teach a_int b_age b_learn b_teach b_int lane\",\"returns\":\"weight 0..100 or excluded=-1\"}," as *u8)
113 p = ca_puts(o, p, "{\"name\":\"reconnect\",\"args\":\"a_band a_mission a_area a_start a_end a_optin b_band b_mission b_area b_start b_end b_optin lane\",\"returns\":\"score 0..100 or excluded=-1\"}," as *u8)
114 p = ca_puts(o, p, "{\"name\":\"lane\",\"args\":\"a_req b_req a_adult b_adult blocked\",\"returns\":\"active lane 0..3 or -1, plus name\"}," as *u8)
115 p = ca_puts(o, p, "{\"name\":\"youth\",\"args\":\"a_band b_band guardian ctx adults\",\"returns\":\"allowed 0|1\"}," as *u8)
116 p = ca_puts(o, p, "{\"name\":\"wards\",\"args\":\"n class0 class1 ..\",\"returns\":\"forbidden field count (0=community-safe)\"}," as *u8)
117 p = ca_puts(o, p, "{\"name\":\"events_rsvp\",\"args\":\"cap is_youth adults_present actor_minor\",\"returns\":\"rsvp code 1|-2|-3|-4\"}," as *u8)
118 p = ca_puts(o, p, "{\"name\":\"poll\",\"args\":\"c0 c1 c2 newchoice\",\"returns\":\"tally after a voter on opt0 re-votes (replace)\"}," as *u8)
119 p = ca_puts(o, p, "{\"name\":\"group_remove\",\"args\":\"actor_role target_role target_is_owner\",\"returns\":\"removed 0|1\"}," as *u8)
120 p = ca_puts(o, p, "{\"name\":\"translate\",\"args\":\"src_lang dst_lang text\",\"returns\":\"inline translation, original preserved (unlimited, no quota)\"}," as *u8)
121 p = ca_puts(o, p, "{\"name\":\"graph\",\"args\":\"a_band a_teach a_learn a_hob a_faith a_optin a_place b_band b_teach b_learn b_hob b_faith b_optin b_place lane\",\"returns\":\"unified interest-graph score 0..100 or excluded=-1 (one model over language+hobby+faith+place)\"}" as *u8)
122 p = ca_puts(o, p, "],\"lanes\":\"0=language 1=friend 2=community 3=date\",\"bands\":\"0=minor 1=adult\",\"langs\":\"0=en 1=ru 2=be 3=uk 4=pl\"}" as *u8)
123 return p
124}
125
126// graph <a: band teach learn hob faith optin place> <b: same 7> <lane> -- the UNIFIED interest graph, one
127// model over language+hobby+faith+place, so two people with no shared language still connect on a shared
128// hobby/faith in the friendship lane. Faith is consent-gated; the minor<->romance wall holds.
129func ca_v_graph(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
130 if argc < 16 { return ca_err(o, off, "graph" as *u8, "usage" as *u8, "graph needs 2 nodes of 7 fields + lane = 15 int args" as *u8) }
131 let a: *i64 = sys_mmap(GX_NODE_SLOTS*8) as *i64
132 let b: *i64 = sys_mmap(GX_NODE_SLOTS*8) as *i64
133 gx_mk(a, ca_ia(argv,argc,1),ca_ia(argv,argc,2),ca_ia(argv,argc,3),ca_ia(argv,argc,4),ca_ia(argv,argc,5),ca_ia(argv,argc,6),ca_ia(argv,argc,7))
134 gx_mk(b, ca_ia(argv,argc,8),ca_ia(argv,argc,9),ca_ia(argv,argc,10),ca_ia(argv,argc,11),ca_ia(argv,argc,12),ca_ia(argv,argc,13),ca_ia(argv,argc,14))
135 let s: i64 = gx_graph_score(a, b, ca_ia(argv,argc,15))
136 var p: i64 = ca_open(o, off, "graph" as *u8)
137 p = ca_puts(o, p, "\"ok\":1," as *u8)
138 p = ca_kv(o, p, "score" as *u8, s)
139 if s<0 { p = ca_puts(o, p, ",\"excluded\":1" as *u8) }
140 p = ca_puts(o, p, "}" as *u8)
141 return p
142}
143
144// translate <src_lang> <dst_lang> <text> -- the language-exchange CORE, exposed for agents. The original is
145// echoed back beside the translation (the learning loop), and it is unlimited BY CONSTRUCTION: there is no
146// quota state anywhere in this call. Unknown words pass through untranslated -- nothing is ever dropped.
147func ca_v_translate(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
148 if argc < 4 { return ca_err(o, off, "translate" as *u8, "usage" as *u8, "translate needs src_lang dst_lang text" as *u8) }
149 let sl: i64 = ca_ia(argv, argc, 1)
150 let dl: i64 = ca_ia(argv, argc, 2)
151 if sl<0 { return ca_err(o, off, "translate" as *u8, "range" as *u8, "src_lang out of 0..4" as *u8) }
152 if sl>CA_LANG_MAX { return ca_err(o, off, "translate" as *u8, "range" as *u8, "src_lang out of 0..4" as *u8) }
153 if dl<0 { return ca_err(o, off, "translate" as *u8, "range" as *u8, "dst_lang out of 0..4" as *u8) }
154 if dl>CA_LANG_MAX { return ca_err(o, off, "translate" as *u8, "range" as *u8, "dst_lang out of 0..4" as *u8) }
155 let src: *u8 = argv[3] as *u8
156 let tr: *u8 = sys_mmap(CA_MAGIC_4096)
157 let tn: i64 = nx_multi_translate(src, ca_slen(src), sl, dl, tr, CA_MAGIC_4095)
158 tr[tn]=0 as u8
159 var p: i64 = ca_open(o, off, "translate" as *u8)
160 p = ca_puts(o, p, "\"ok\":1,\"original\":\"" as *u8)
161 p = ca_esc_json(o, p, src)
162 p = ca_puts(o, p, "\",\"translation\":\"" as *u8)
163 p = ca_esc_json(o, p, tr)
164 p = ca_puts(o, p, "\",\"unlimited\":1}" as *u8)
165 return p
166}
167
168// ---- verb handlers (each PURE: builds JSON from args, calls the engine, never mutates shared state) ----
169
170func ca_v_match(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
171 if argc < 10 { return ca_err(o, off, "match" as *u8, "usage" as *u8, "match needs 9 int args" as *u8) }
172 let w: i64 = mx_match_weight(ca_ia(argv,argc,1),ca_ia(argv,argc,2),ca_ia(argv,argc,3),ca_ia(argv,argc,4),
173 ca_ia(argv,argc,5),ca_ia(argv,argc,6),ca_ia(argv,argc,7),ca_ia(argv,argc,8),ca_ia(argv,argc,9))
174 var p: i64 = ca_open(o, off, "match" as *u8)
175 p = ca_puts(o, p, "\"ok\":1," as *u8)
176 p = ca_kv(o, p, "weight" as *u8, w)
177 if w<0 { p = ca_puts(o, p, ",\"excluded\":1" as *u8) }
178 p = ca_puts(o, p, "}" as *u8)
179 return p
180}
181
182func ca_v_reconnect(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
183 if argc < 14 { return ca_err(o, off, "reconnect" as *u8, "usage" as *u8, "reconnect needs 13 int args" as *u8) }
184 let a: *i64 = sys_mmap(RC_REC_SLOTS*8) as *i64
185 let b: *i64 = sys_mmap(RC_REC_SLOTS*8) as *i64
186 // tokens/confirm fixed at 0 in v1 (the confirmed-edge bonus is not exposed): a=1/0, b=2/0
187 rc_mk(a, ca_ia(argv,argc,1), ca_ia(argv,argc,2), ca_ia(argv,argc,3), ca_ia(argv,argc,4), ca_ia(argv,argc,5), ca_ia(argv,argc,6), 1, 0)
188 rc_mk(b, ca_ia(argv,argc,7), ca_ia(argv,argc,8), ca_ia(argv,argc,9), ca_ia(argv,argc,10), ca_ia(argv,argc,11), ca_ia(argv,argc,12), 2, 0)
189 let s: i64 = rc_lane(a, b, ca_ia(argv,argc,13))
190 var p: i64 = ca_open(o, off, "reconnect" as *u8)
191 p = ca_puts(o, p, "\"ok\":1," as *u8)
192 p = ca_kv(o, p, "score" as *u8, s)
193 if s<0 { p = ca_puts(o, p, ",\"excluded\":1" as *u8) }
194 p = ca_puts(o, p, "}" as *u8)
195 return p
196}
197
198func ca_v_lane(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
199 if argc < 6 { return ca_err(o, off, "lane" as *u8, "usage" as *u8, "lane needs 5 int args" as *u8) }
200 let l: i64 = ln_active_lane(ca_ia(argv,argc,1),ca_ia(argv,argc,2),ca_ia(argv,argc,3),ca_ia(argv,argc,4),ca_ia(argv,argc,5))
201 var p: i64 = ca_open(o, off, "lane" as *u8)
202 p = ca_puts(o, p, "\"ok\":1," as *u8)
203 p = ca_kv(o, p, "lane" as *u8, l)
204 p = ca_puts(o, p, ",\"name\":\"" as *u8)
205 p = ca_puts(o, p, ln_lane_name(l))
206 p = ca_puts(o, p, "\"}" as *u8)
207 return p
208}
209
210func ca_v_youth(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
211 if argc < 6 { return ca_err(o, off, "youth" as *u8, "usage" as *u8, "youth needs 5 int args" as *u8) }
212 let a: i64 = yw_may_channel(ca_ia(argv,argc,1),ca_ia(argv,argc,2),ca_ia(argv,argc,3),ca_ia(argv,argc,4),ca_ia(argv,argc,5))
213 var p: i64 = ca_open(o, off, "youth" as *u8)
214 p = ca_puts(o, p, "\"ok\":1," as *u8)
215 p = ca_kv(o, p, "allowed" as *u8, a)
216 p = ca_puts(o, p, "}" as *u8)
217 return p
218}
219
220func ca_v_wards(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
221 if argc < 2 { return ca_err(o, off, "wards" as *u8, "usage" as *u8, "wards needs n then n class ints" as *u8) }
222 let n: i64 = ca_ia(argv,argc,1)
223 if n < 0 { return ca_err(o, off, "wards" as *u8, "range" as *u8, "n must be >= 0" as *u8) }
224 if n > FR_MAX { return ca_err(o, off, "wards" as *u8, "range" as *u8, "n exceeds FR_MAX" as *u8) }
225 if argc < 2+n { return ca_err(o, off, "wards" as *u8, "usage" as *u8, "fewer class args than n" as *u8) }
226 let classes: *i64 = sys_mmap(FR_MAX*8) as *i64
227 var i: i64=0
228 while i<n { classes[i]=ca_ia(argv,argc,2+i); i=i+1 }
229 let f: i64 = wd_forbidden_count(classes, n)
230 var p: i64 = ca_open(o, off, "wards" as *u8)
231 p = ca_puts(o, p, "\"ok\":1," as *u8)
232 p = ca_kv(o, p, "forbidden" as *u8, f)
233 p = ca_puts(o, p, ",\"community_safe\":" as *u8)
234 if f==0 { p = ca_puts(o, p, "1" as *u8) } else { p = ca_puts(o, p, "0" as *u8) }
235 p = ca_puts(o, p, "}" as *u8)
236 return p
237}
238
239// events_rsvp cap is_youth adults_present actor_minor -> the REAL ev_rsvp verdict against a minimal world
240func ca_v_events(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
241 if argc < 5 { return ca_err(o, off, "events_rsvp" as *u8, "usage" as *u8, "events_rsvp needs cap is_youth adults_present actor_minor" as *u8) }
242 let cap: i64 = ca_ia(argv,argc,1)
243 let isyth: i64 = ca_ia(argv,argc,2)
244 let adults: i64 = ca_ia(argv,argc,3)
245 let amin: i64 = ca_ia(argv,argc,4)
246 let e: *i64 = sys_mmap(16*8) as *i64
247 let rsev: *i64 = sys_mmap(RS_MAX*8) as *i64
248 let rsus: *i64 = sys_mmap(RS_MAX*8) as *i64
249 let rsac: *i64 = sys_mmap(RS_MAX*8) as *i64
250 let nrs: *i64 = sys_mmap(8) as *i64
251 let evgrp: *i64 = sys_mmap(EV_MAX*8) as *i64
252 let evcap: *i64 = sys_mmap(EV_MAX*8) as *i64
253 let evyth: *i64 = sys_mmap(EV_MAX*8) as *i64
254 let mbgrp: *i64 = sys_mmap(MB_MAX*8) as *i64
255 let mbus: *i64 = sys_mmap(MB_MAX*8) as *i64
256 let nmb: *i64 = sys_mmap(8) as *i64
257 let uaus: *i64 = sys_mmap(UA_MAX*8) as *i64
258 let uamin: *i64 = sys_mmap(UA_MAX*8) as *i64
259 let nua: *i64 = sys_mmap(8) as *i64
260 e[CX_RSEV]=rsev as i64; e[CX_RSUS]=rsus as i64; e[CX_RSAC]=rsac as i64; e[CX_NRS]=nrs as i64
261 e[CX_EVGRP]=evgrp as i64; e[CX_EVCAP]=evcap as i64; e[CX_EVYTH]=evyth as i64
262 e[CX_MBGRP]=mbgrp as i64; e[CX_MBUS]=mbus as i64; e[CX_NMB]=nmb as i64
263 e[CX_UAUS]=uaus as i64; e[CX_UAMIN]=uamin as i64; e[CX_NUA]=nua as i64
264 evgrp[0]=1; evcap[0]=cap; evyth[0]=isyth
265 // members 1..40 in group 1; adults 1..20 (band 0), youth 21..40 (band 1); actor id 50
266 var i: i64=0
267 var m: i64=0
268 var u: i64=0
269 while i<20 { mbgrp[m]=1; mbus[m]=1+i; m=m+1; uaus[u]=1+i; uamin[u]=0; u=u+1; i=i+1 }
270 i=0
271 while i<20 { mbgrp[m]=1; mbus[m]=21+i; m=m+1; uaus[u]=21+i; uamin[u]=1; u=u+1; i=i+1 }
272 // actor
273 mbgrp[m]=1; mbus[m]=50; m=m+1
274 uaus[u]=50; uamin[u]=amin; u=u+1
275 nmb[0]=m; nua[0]=u
276 // seed `adults` adult RSVPs already present
277 var k: i64=0
278 i=0
279 while i<adults { rsev[k]=0; rsus[k]=1+i; rsac[k]=1; k=k+1; i=i+1 }
280 nrs[0]=k
281 let rc: i64 = ev_rsvp(e, 0, 50)
282 var p: i64 = ca_open(o, off, "events_rsvp" as *u8)
283 p = ca_puts(o, p, "\"ok\":1," as *u8)
284 p = ca_kv(o, p, "code" as *u8, rc)
285 p = ca_puts(o, p, ",\"admitted\":" as *u8)
286 if rc==1 { p = ca_puts(o, p, "1" as *u8) } else { p = ca_puts(o, p, "0" as *u8) }
287 p = ca_puts(o, p, "}" as *u8)
288 return p
289}
290
291// poll c0 c1 c2 newchoice -> seed counts, a voter currently on opt0 re-votes newchoice, return tally
292func ca_v_poll(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
293 if argc < 5 { return ca_err(o, off, "poll" as *u8, "usage" as *u8, "poll needs c0 c1 c2 newchoice" as *u8) }
294 let c0: i64 = ca_ia(argv,argc,1)
295 let c1: i64 = ca_ia(argv,argc,2)
296 let c2: i64 = ca_ia(argv,argc,3)
297 let nc: i64 = ca_ia(argv,argc,4)
298 if c0+c1+c2 >= PL_MAXV { return ca_err(o, off, "poll" as *u8, "range" as *u8, "too many ballots" as *u8) }
299 let bv: *i64 = sys_mmap(PL_MAXV*8) as *i64
300 let bc: *i64 = sys_mmap(PL_MAXV*8) as *i64
301 let nb: *i64 = sys_mmap(8) as *i64
302 let meta: *i64 = sys_mmap(8*8) as *i64
303 nb[0]=0; meta[0]=0; meta[1]=3; meta[2]=1; meta[3]=999
304 var id: i64=1
305 var i: i64=0
306 while i<c0 { pl_vote(bv,bc,nb,meta,id,0); id=id+1; i=i+1 }
307 i=0
308 while i<c1 { pl_vote(bv,bc,nb,meta,id,1); id=id+1; i=i+1 }
309 i=0
310 while i<c2 { pl_vote(bv,bc,nb,meta,id,2); id=id+1; i=i+1 }
311 // voter id 1 is on opt0 (if c0>0); re-vote them to nc -> REPLACE, not append
312 if c0 > 0 { pl_vote(bv,bc,nb,meta,1,nc) }
313 var p: i64 = ca_open(o, off, "poll" as *u8)
314 p = ca_puts(o, p, "\"ok\":1,\"tally\":[" as *u8)
315 p = ca_putn(o, p, pl_count(bc,nb,0))
316 p = ca_puts(o, p, "," as *u8)
317 p = ca_putn(o, p, pl_count(bc,nb,1))
318 p = ca_puts(o, p, "," as *u8)
319 p = ca_putn(o, p, pl_count(bc,nb,2))
320 p = ca_puts(o, p, "]," as *u8)
321 p = ca_kv(o, p, "total" as *u8, nb[0])
322 p = ca_puts(o, p, "}" as *u8)
323 return p
324}
325
326// group_remove actor_role target_role target_is_owner -> real grp_remove over a minimal ladder
327func ca_v_group(o: *u8, off: i64, argv: *i64, argc: i64) -> i64 {
328 if argc < 4 { return ca_err(o, off, "group_remove" as *u8, "usage" as *u8, "group_remove needs actor_role target_role target_is_owner" as *u8) }
329 let ar: i64 = ca_ia(argv,argc,1)
330 let tr: i64 = ca_ia(argv,argc,2)
331 let towner: i64 = ca_ia(argv,argc,3)
332 let meta: *i64 = sys_mmap(8*8) as *i64
333 let mem: *i64 = sys_mmap(64*8) as *i64
334 grp_create(meta, mem, 1, MODE_GROUP, LANE_LANG) // owner = uid 1
335 grp_add(meta, mem, 1, 8, 0) // actor uid 8
336 grp_promote(meta, mem, 1, 8, ar)
337 var target: i64 = 1 // default target = owner
338 if towner==0 {
339 grp_add(meta, mem, 1, 9, 0) // target uid 9
340 grp_promote(meta, mem, 1, 9, tr)
341 target = 9
342 }
343 let rc: i64 = grp_remove(meta, mem, 8, target)
344 var p: i64 = ca_open(o, off, "group_remove" as *u8)
345 p = ca_puts(o, p, "\"ok\":1," as *u8)
346 p = ca_kv(o, p, "removed" as *u8, rc)
347 p = ca_puts(o, p, "}" as *u8)
348 return p
349}
350
351// THE DISPATCHER: argv[0] is the verb, args at argv[1..]. Returns the JSON byte length in out.
352func ca_dispatch(argc: i64, argv: *i64, out: *u8) -> i64 {
353 if argc < 1 { return ca_help(out, 0) }
354 let v: *u8 = argv[0] as *u8
355 if ca_streq(v, "help" as *u8)==1 { return ca_help(out, 0) }
356 if ca_streq(v, "caps" as *u8)==1 { return ca_help(out, 0) }
357 if ca_streq(v, "match" as *u8)==1 { return ca_v_match(out, 0, argv, argc) }
358 if ca_streq(v, "reconnect" as *u8)==1 { return ca_v_reconnect(out, 0, argv, argc) }
359 if ca_streq(v, "lane" as *u8)==1 { return ca_v_lane(out, 0, argv, argc) }
360 if ca_streq(v, "youth" as *u8)==1 { return ca_v_youth(out, 0, argv, argc) }
361 if ca_streq(v, "wards" as *u8)==1 { return ca_v_wards(out, 0, argv, argc) }
362 if ca_streq(v, "events_rsvp" as *u8)==1 { return ca_v_events(out, 0, argv, argc) }
363 if ca_streq(v, "poll" as *u8)==1 { return ca_v_poll(out, 0, argv, argc) }
364 if ca_streq(v, "group_remove" as *u8)==1 { return ca_v_group(out, 0, argv, argc) }
365 if ca_streq(v, "translate" as *u8)==1 { return ca_v_translate(out, 0, argv, argc) }
366 if ca_streq(v, "graph" as *u8)==1 { return ca_v_graph(out, 0, argv, argc) }
367 return ca_err(out, 0, v, "unknown_verb" as *u8, "call help for the verb catalog" as *u8)
368}