nx_writehub_grow.nx source
↩ module page · 192 lines · 11092 B
1// nx_writehub_grow.nx -- WRITING HUB rung R3: the sovereign SYSTEM-DATA GROWTH organ. Operator 2026-06-30:
2// "integrate with system data and EXPAND it so we can grow clothing etc." Reads a new catalog entry
3// (knowledge/staging/writehub_grow_req.txt, key=value lines) and emits an ADDITIVE svc-content add-request:
4// a NEW <entity>_key + prompt_fragment + zimage_sentence (the writer<->image bridge) + keyword aliases.
5// Stages the exact request to knowledge/staging/writehub_grow_out.json and POSTs it to svc-content:8300
6// /v2/content/add-<entity> over sovereign HTTP (127.0.0.1, no curl/shell). Additive ONLY (Rule 13: a NEW key,
7// never an overwrite; Rule 25: MORE options, never fewer). Composes nx_http_client (same primitive as
8// nx_writehub_seat = consolidation). HONEST: the live add-outfit endpoint sets only 4 cols (no zimage_sentence)
9// -> the staged .json carries it for the EXTENDED endpoint (API to extend, build-if-missing); the live POST body
10// uses exactly the endpoint's fields so it never 422s. The add-LOCATION endpoint has a base_prompt-vs-
11// prompt_fragment column bug -> grow outfits/poses until it is fixed. license_tier: ORIGINAL module: nishi-core.writehub.grow
12import "nx_http_client.nx"
13import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
14import "nx_gate.nx"
15const K_MAGIC_262144: i64 = 262144
16const K_MAGIC_4096: i64 = 4096
17const K_MAGIC_2048: i64 = 2048
18const K_MAGIC_65536: i64 = 65536
19const K_MAGIC_131072: i64 = 131072
20const K_MAGIC_8300: i64 = 8300
21const K_MAGIC_1048576: i64 = 1048576
22
23func wg_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
24func wg_atoi3(buf: *u8, off: i64) -> i64 { var v: i64=0; var i: i64=0; while i<3 { let d: i64=buf[off+i] as i64; if d>=48 { if d<=57 { v=v*10+(d-48) } } i=i+1 } return v }
25func wg_drain(fd: i64, buf: *u8, cap: i64) -> i64 { var off: i64=0; var go: i64=1; while go==1 { if off>=cap { go=0 } else { let r: i64=sys_read(fd, ((buf as i64)+off) as *u8, cap-off); if r<=0 { go=0 } else { off=off+r } } } return off }
26func wg_app(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var i: i64=0; while s[i]!=(0 as u8){ dst[o]=s[i]; o=o+1; i=i+1 } return o }
27func wg_appc(dst: *u8, off: i64, ch: i64) -> i64 { dst[off]=ch as u8; return off+1 }
28func wg_app_range(dst: *u8, off: i64, src: *u8, a: i64, b: i64) -> i64 { var o: i64=off; var i: i64=a; while i<b { dst[o]=src[i]; o=o+1; i=i+1 } return o }
29// JSON-escape src[0..len) into dst at off
30func wg_app_esc(dst: *u8, off: i64, src: *u8, len: i64) -> i64 {
31 var o: i64=off; var i: i64=0
32 while i<len {
33 let c: i64 = src[i] as i64
34 if c==34 { o=wg_appc(dst,o,92); o=wg_appc(dst,o,34) }
35 else { if c==92 { o=wg_appc(dst,o,92); o=wg_appc(dst,o,92) }
36 else { if c==10 { o=wg_appc(dst,o,92); o=wg_appc(dst,o,110) }
37 else { if c==13 { o=o }
38 else { if c<32 { o=o } else { dst[o]=src[i]; o=o+1 } } } } }
39 i=i+1
40 }
41 return o
42}
43func wg_appq_esc(dst: *u8, off: i64, src: *u8, len: i64) -> i64 { var o: i64=off; o=wg_appc(dst,o,34); o=wg_app_esc(dst,o,src,len); o=wg_appc(dst,o,34); return o }
44func wg_appq(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; o=wg_appc(dst,o,34); o=wg_app(dst,o,s); o=wg_appc(dst,o,34); return o }
45
46// key= match at i (i must be a line start); return offset to the char after '=' (0 = no match)
47func wg_kv_match(buf: *u8, i: i64, n: i64, key: *u8) -> i64 {
48 var j: i64=0
49 while key[j]!=(0 as u8) { if i+j>=n { return 0 } if buf[i+j]!=key[j] { return 0 } j=j+1 }
50 if i+j>=n { return 0 }
51 if buf[i+j]!=(61 as u8) { return 0 }
52 return j+1
53}
54// copy the value of `key` (first occurrence at a line start) into out (null-term); return length
55func wg_kv_get(buf: *u8, n: i64, key: *u8, out: *u8, outcap: i64) -> i64 {
56 var i: i64=0; var atline: i64=1
57 while i<n {
58 if atline==1 {
59 let off: i64 = wg_kv_match(buf, i, n, key)
60 if off>0 {
61 var s: i64=i+off; var o: i64=0; var go: i64=1
62 while go==1 {
63 if s>=n { go=0 } else {
64 let c: i64=buf[s] as i64
65 if c==10 { go=0 } else { if c==13 { go=0 } else { if o<outcap-1 { out[o]=buf[s]; o=o+1 } s=s+1 } }
66 }
67 }
68 out[o]=0 as u8
69 return o
70 }
71 }
72 if buf[i]==(10 as u8) { atline=1 } else { atline=0 }
73 i=i+1
74 }
75 out[0]=0 as u8
76 return 0
77}
78// append ["a","b",...] from a comma-separated list (spaces after commas trimmed)
79func wg_app_keywords(dst: *u8, off: i64, kw: *u8, kwlen: i64) -> i64 {
80 var o: i64=off
81 o=wg_appc(dst,o,91)
82 var i: i64=0; var tokstart: i64=0; var first: i64=1; var go: i64=1
83 while go==1 {
84 var atend: i64=0
85 if i>=kwlen { atend=1 }
86 var boundary: i64=atend
87 if atend==0 { if kw[i]==(44 as u8) { boundary=1 } }
88 if boundary==1 {
89 var ts: i64=tokstart // trim leading spaces of the token [tokstart..i)
90 var found: i64=0
91 while found==0 { if ts>=i { found=1 } else { if kw[ts]==(32 as u8) { ts=ts+1 } else { found=1 } } }
92 if i>ts {
93 if first==0 { o=wg_appc(dst,o,44) }
94 o=wg_appc(dst,o,34); o=wg_app_range(dst,o,kw,ts,i); o=wg_appc(dst,o,34)
95 first=0
96 }
97 tokstart=i+1
98 }
99 if atend==1 { go=0 } else { i=i+1 }
100 }
101 o=wg_appc(dst,o,93)
102 return o
103}
104
105func main() -> i64 {
106 let rfd: i64 = sys_openat_rd("knowledge/staging/writehub_grow_req.txt" as *u8)
107 if rfd < 0 { gw("GROW: no knowledge/staging/writehub_grow_req.txt (key=value lines: entity/key/name/category/fragment/zimage/keywords)\n" as *u8); sys_exit(1); return 1 }
108 let rb: *u8 = sys_mmap(K_MAGIC_262144)
109 let rn: i64 = sys_read(rfd, rb, K_MAGIC_262144)
110 sys_close(rfd)
111 if rn <= 0 { gw("GROW: empty grow_req\n" as *u8); sys_exit(1); return 1 }
112
113 let entity: *u8 = sys_mmap(64)
114 let key: *u8 = sys_mmap(256)
115 let name: *u8 = sys_mmap(512)
116 let category: *u8 = sys_mmap(256)
117 let frag: *u8 = sys_mmap(K_MAGIC_4096)
118 let zimg: *u8 = sys_mmap(K_MAGIC_4096)
119 let kw: *u8 = sys_mmap(K_MAGIC_2048)
120 wg_kv_get(rb, rn, "entity" as *u8, entity, 64)
121 let keyn: i64 = wg_kv_get(rb, rn, "key" as *u8, key, 256)
122 wg_kv_get(rb, rn, "name" as *u8, name, 512)
123 wg_kv_get(rb, rn, "category" as *u8, category, 256)
124 let fragn: i64 = wg_kv_get(rb, rn, "fragment" as *u8, frag, K_MAGIC_4096)
125 let zimgn: i64 = wg_kv_get(rb, rn, "zimage" as *u8, zimg, K_MAGIC_4096)
126 let kwn: i64 = wg_kv_get(rb, rn, "keywords" as *u8, kw, K_MAGIC_2048)
127
128 if keyn <= 0 { gw("GROW: REJECT -- missing required 'key' (additive growth needs a NEW unique key, Rule 13)\n" as *u8); sys_exit(2); return 2 }
129 if fragn <= 0 { gw("GROW: REJECT -- missing required 'fragment' (prompt_fragment NOT NULL)\n" as *u8); sys_exit(2); return 2 }
130
131 // resolve entity -> path + key-field (default outfit). pose shares the same shape.
132 var path: *u8 = "/v2/content/add-outfit" as *u8
133 var keyfield: *u8 = "outfit_key" as *u8
134 if entity[0]==(112 as u8) { path = "/v2/content/add-pose" as *u8; keyfield = "pose_key" as *u8 } // 'p' -> pose
135
136 // build the LIVE POST body = EXACTLY the endpoint's fields (no extra -> never 422)
137 let body: *u8 = sys_mmap(K_MAGIC_65536)
138 var bo: i64 = 0
139 bo=wg_appc(body,bo,123)
140 bo=wg_appq(body,bo,keyfield); bo=wg_appc(body,bo,58); bo=wg_appq_esc(body,bo,key,wg_strlen(key))
141 bo=wg_appc(body,bo,44); bo=wg_appq(body,bo,"name" as *u8); bo=wg_appc(body,bo,58); bo=wg_appq_esc(body,bo,name,wg_strlen(name))
142 bo=wg_appc(body,bo,44); bo=wg_appq(body,bo,"prompt_fragment" as *u8); bo=wg_appc(body,bo,58); bo=wg_appq_esc(body,bo,frag,fragn)
143 bo=wg_appc(body,bo,44); bo=wg_appq(body,bo,"category" as *u8); bo=wg_appc(body,bo,58); bo=wg_appq_esc(body,bo,category,wg_strlen(category))
144 bo=wg_appc(body,bo,44); bo=wg_appq(body,bo,"keywords" as *u8); bo=wg_appc(body,bo,58); bo=wg_app_keywords(body,bo,kw,kwn)
145 bo=wg_appc(body,bo,125)
146 body[bo]=0 as u8
147
148 // build the FULL staged entry = POST body + zimage_sentence (the image bridge, for the extended endpoint)
149 let full: *u8 = sys_mmap(K_MAGIC_131072)
150 var fo: i64 = 0
151 fo=wg_appc(full,fo,123)
152 fo=wg_appq(full,fo,keyfield); fo=wg_appc(full,fo,58); fo=wg_appq_esc(full,fo,key,wg_strlen(key))
153 fo=wg_appc(full,fo,44); fo=wg_appq(full,fo,"name" as *u8); fo=wg_appc(full,fo,58); fo=wg_appq_esc(full,fo,name,wg_strlen(name))
154 fo=wg_appc(full,fo,44); fo=wg_appq(full,fo,"prompt_fragment" as *u8); fo=wg_appc(full,fo,58); fo=wg_appq_esc(full,fo,frag,fragn)
155 fo=wg_appc(full,fo,44); fo=wg_appq(full,fo,"zimage_sentence" as *u8); fo=wg_appc(full,fo,58); fo=wg_appq_esc(full,fo,zimg,zimgn)
156 fo=wg_appc(full,fo,44); fo=wg_appq(full,fo,"category" as *u8); fo=wg_appc(full,fo,58); fo=wg_appq_esc(full,fo,category,wg_strlen(category))
157 fo=wg_appc(full,fo,44); fo=wg_appq(full,fo,"enabled" as *u8); fo=wg_appc(full,fo,58); fo=wg_app(full,fo,"1" as *u8)
158 fo=wg_appc(full,fo,44); fo=wg_appq(full,fo,"keywords" as *u8); fo=wg_appc(full,fo,58); fo=wg_app_keywords(full,fo,kw,kwn)
159 fo=wg_appc(full,fo,125)
160 full[fo]=0 as u8
161
162 let sfd: i64 = sys_openat_wr("knowledge/staging/writehub_grow_out.json" as *u8, 0x1a4)
163 if sfd >= 0 { sys_write(sfd, full, fo); sys_close(sfd) }
164
165 gw("GROW entity=" as *u8); gw(entity); gw(" key=" as *u8); gw(key); gw(" (additive, new key)\n" as *u8)
166 gw(" staged full entry (with zimage_sentence bridge) -> knowledge/staging/writehub_grow_out.json\n" as *u8)
167 gw(" POST body: " as *u8); gw(body); gw("\n" as *u8)
168
169 // POST the additive add-request to svc-content:8300 over sovereign HTTP
170 let addr: *u8 = sys_mmap(16)
171 nx_http_client_sockaddr_ipv4(addr, 127, 0, 0, 1, K_MAGIC_8300)
172 let fd: i64 = sys_socket(2, 1, 0)
173 if fd < 0 { gw("GROW socket-fail\n" as *u8); sys_exit(3); return 3 }
174 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 {
175 sys_close(fd)
176 gw("GROW -> CONN-FAIL (svc-content not listening on 127.0.0.1:8300) -- request is BUILT+staged; bring svc-content up to grow live\n" as *u8)
177 sys_exit(2); return 2
178 }
179 let ct: *u8 = "application/json" as *u8
180 let req: *u8 = sys_mmap(K_MAGIC_131072)
181 let reqlen: i64 = nx_http_client_build_request_post(path, wg_strlen(path), "localhost" as *u8, 9, ct, wg_strlen(ct), body, bo, req)
182 sys_write(fd, req, reqlen)
183 let cap: i64 = K_MAGIC_1048576
184 let buf: *u8 = sys_mmap(cap)
185 let nn: i64 = wg_drain(fd, buf, cap)
186 sys_close(fd)
187 var status: i64 = 0
188 if nn > 12 { status = wg_atoi3(buf, 9) }
189 gw("GROW POST " as *u8); gw(path); gw(" -> status=" as *u8); gn(status); gw(" bytes=" as *u8); gn(nn); gw("\n" as *u8)
190 if status >= 200 { if status < 300 { gw("GROW OK -- catalog grew by one (additive); the image-gen now sees it via prompt_fragment\n" as *u8); sys_exit(0); return 0 } }
191 sys_exit(3); return 3
192}