code wiki / _hdl_build / nx_sovgit_push.nx
nx_sovgit_push.nx source
↩ module page · 1139 lines · 50886 B
1// nx_sovgit_push.nx -- ★SOVGIT F222: the SOVEREIGN GIT PUSHER. Snapshots a working tree and pushes it to a
2// sovgit host speaking the git receive-pack CLIENT wire, with ZERO /usr/bin/git anywhere in the path.
3// This is the no-git-law end-state for the replication leg: walk -> X0 sha256 objects -> X1b packfile ->
4// pkt-line ref-update + pack over HTTP -> report-status. The ark's crash-durability beat becomes sovereign.
5//
6// WHY A SNAPSHOT (not a git-history mirror): stock git CANNOT push a sha1 repo to a sha256 remote (no interop),
7// so the sovereign leg re-frames the working tree as sha256 objects. Each push chains a commit onto the remote
8// ref (parent = the advertised old sha) so history is preserved additively (rule 13) -- exactly the ark's job.
9//
10// SCALE ENVELOPE (declared in output, guarded in code, fail LOUD -- never a silent partial snapshot):
11// entries per directory <= 20000 name bytes per directory <= 2 MiB tree depth <= 32
12// single file <= 64 MiB pack <= 128 MiB path <= 4096
13// skipped by design: .git, symlinks + special files, and SECRET MATERIAL (deny-by-construction:
14// secret/passw/token/credential/.pem/.cap/.key/_key(s)/privkey/id_rsa/id_ed25519/_seed.bin/opaque)
15// -- every exclusion is COUNTED and NAMED on stderr, never silently dropped
16// ⚠the SERVER's request envelope also binds (nx_sovgit_git req buffer); a push above it is refused there.
17// Transport rung 1 = loopback TCP (127.0.0.1:<port>) -- the daemon is wall-only by design; the TLS/edge
18// transport for off-box pushes is the sibling rung (compose mcl_req-class primitives).
19// license_tier: ORIGINAL
20import "nx_sovgit_obj.nx"
21import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
22// TLS transport (rung 2) -- the SAME request bytes over sovereign TLS-1.3 so an OFF-BOX (laptop) ark
23// can push to the hub through the edge. Composes the proven nx_mgmt_client stack, nothing new invented.
24import "nx_acme_http.nx"
25import "nx_https_url_for_fetch.nx"
26import "nx_https_url_connect.nx"
27import "nx_tls13_client_session_run.nx"
28import "nx_tls13_client_session.nx"
29import "nx_tls13.nx"
30import "nx_tls13_record.nx"
31import "nx_tls13_read_record_from_fd.nx"
32import "nx_csprng.nx"
33const SGP_MAGIC_131072: i64 = 131072
34const SGP_MAGIC_61440: i64 = 61440
35const SGP_MAGIC_16384: i64 = 16384
36const SGP_MAGIC_32768: i64 = 32768
37const SGP_MAGIC_16645: i64 = 16645
38const SGP_MAGIC_2048: i64 = 2048
39const SGP_MAGIC_18691: i64 = 18691
40const SGP_MAGIC_4194304: i64 = 4194304
41const SGP_MAGIC_8192: i64 = 8192
42const SGP_MAGIC_1024: i64 = 1024
43const SGP_MAGIC_65536: i64 = 65536
44const SGP_MAGIC_4096: i64 = 4096
45
46// ★MEASURED 2026-07-20 (debt seq334): pushing a body larger than this THROUGH THE EDGE does not merely
47// fail -- it drove sites.elf into a crash loop and took every public site down twice. Bodies <=1.4MB
48// succeeded consistently. So the client REFUSES to send an over-safe body over the edge rather than
49// discover the limit by knocking the web surface over. Loopback (on-box, no edge in the path) is
50// unaffected. Remove this only when the edge itself refuses over-cap bodies with a bounded 413.
51const SGP_EDGE_SAFE: i64 = 1400000
52const SGP_MAX_OBJ: i64 = 200000
53const SGP_HS_SIZE: i64 = 524288
54const SGP_MAX_ENT: i64 = 20000
55const SGP_NAMEARENA: i64 = 2097152
56const SGP_MAX_DEPTH: i64 = 32
57const SGP_MAX_FILE: i64 = 67108864
58const SGP_PACK_CAP: i64 = 134217728
59const SGP_PATHBUF: i64 = 4096
60const SGP_RESP: i64 = 65536
61
62// ---- byte / io helpers ----
63func sgp_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
64func sgp_we(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 }
65func sgp_wn(v: i64) -> i64 {
66 let b: *u8 = sys_mmap(32)
67 var x: i64 = v
68 var neg: i64 = 0
69 if x < 0 { neg = 1; x = 0 - x }
70 var i: i64 = 31
71 if x == 0 { b[i] = 48 as u8; i = i - 1 }
72 while x > 0 { b[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 }
73 if neg == 1 { b[i] = 45 as u8; i = i - 1 }
74 sys_write(1, (b as i64 + i + 1) as *u8, 31 - i)
75 sys_munmap(b, 32)
76 return 0
77}
78func sgp_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
79func sgp_cat(dst: *u8, at: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[at + i] = s[i]; i = i + 1 } return at + i }
80func sgp_dec(out: *u8, at: i64, v: i64) -> i64 {
81 let tmp: *u8 = sys_mmap(32)
82 var x: i64 = v
83 var i: i64 = 31
84 if x == 0 { tmp[i] = 48 as u8; i = i - 1 }
85 while x > 0 { tmp[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 }
86 var a: i64 = at
87 var j: i64 = i + 1
88 while j < 32 { out[a] = tmp[j]; a = a + 1; j = j + 1 }
89 sys_munmap(tmp, 32)
90 return a
91}
92func sgp_atoi(s: *u8) -> i64 {
93 var v: i64 = 0
94 var i: i64 = 0
95 while s[i] != (0 as u8) {
96 let c: i64 = s[i] as i64
97 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } }
98 i = i + 1
99 }
100 return v
101}
102func sgp_streq(a: *u8, b: *u8) -> i64 {
103 var i: i64 = 0
104 var r: i64 = 1
105 var go: i64 = 1
106 while go == 1 {
107 let ca: i64 = a[i] as i64
108 let cb: i64 = b[i] as i64
109 if ca != cb { r = 0; go = 0 } else { if ca == 0 { go = 0 } else { i = i + 1 } }
110 }
111 return r
112}
113// ---- SECRET EXCLUSION, deny-by-construction (rule 12: defensive at boundaries) ----
114// A tool that replicates a filesystem into a remote repo IS a boundary: without this, one push
115// silently exfiltrates private keys into a clonable repo forever (git history is additive -- an
116// accidental key push cannot be un-published). Mirrors the compiled-in nx_fs_write deny-list.
117// Needles are TARGETED so source files that merely IMPLEMENT crypto (nx_ed25519.nx) still ship,
118// while key MATERIAL (opaque_keys.bin, node_ed25519_seed.bin, tools_cap_secret.key) never does.
119// Every exclusion is COUNTED and NAMED on stderr -- over-denying is safe, silent denying is not.
120func sgp_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c }
121func sgp_needle_ci(name: *u8, needle: *u8) -> i64 {
122 let n: i64 = sgp_slen(name)
123 let m: i64 = sgp_slen(needle)
124 if m == 0 { return 0 }
125 var i: i64 = 0
126 while i + m <= n {
127 var k: i64 = 0
128 while k < m { if sgp_lc(name[i + k] as i64) != sgp_lc(needle[k] as i64) { k = m + 9 } else { k = k + 1 } }
129 if k == m { return 1 }
130 i = i + 1
131 }
132 return 0
133}
134// ---- DATA-DRIVEN IGNORE (rule 11: policy is DATA, never buried in code) ----
135// `<srcdir>/.sovgitignore`, one substring pattern per line (# comments + blanks skipped). Matched
136// case-insensitively against the ENTRY NAME. Needed for real trees: a huge constantly-rewritten log
137// (memory/worklog/worklog.tsv, 15MB) would blow the per-beat wire budget on every single beat.
138// Like the secret list, every exclusion is COUNTED and NAMED -- silent dropping is never acceptable.
139// pst[7] = pattern buffer ptr, pst[8] = pattern count, pst[9] = entries ignored.
140func sgp_load_ignore(srcdir: *u8, ibuf: *u8, icap: i64, pst: *i64) -> i64 {
141 pst[7] = 0
142 pst[8] = 0
143 pst[9] = 0
144 let p: *u8 = sys_mmap(SGP_PATHBUF)
145 var o: i64 = sgp_cat(p, 0, srcdir)
146 o = sgp_cat(p, o, "/.sovgitignore" as *u8)
147 p[o] = 0 as u8
148 let szp: *i64 = sys_mmap(16) as *i64
149 let b: *u8 = sgp_read_file(p, szp)
150 sys_munmap(p, SGP_PATHBUF)
151 if (b as i64) == 0 { sys_munmap(szp as *u8, 16); return 0 }
152 let n: i64 = szp[0]
153 sys_munmap(szp as *u8, 16)
154 var out: i64 = 0
155 var cnt: i64 = 0
156 var i: i64 = 0
157 while i < n {
158 // one line: [s, e) where e is the '\n' index (or n). The position MUST survive the scan --
159 // collapsing the loop variable to sentinel loses it and yields ONE pattern = the whole file.
160 let s: i64 = i
161 var e: i64 = s
162 var scan: i64 = 1
163 while scan == 1 {
164 if e >= n { scan = 0 } else {
165 if b[e] == (10 as u8) { scan = 0 } else { e = e + 1 }
166 }
167 }
168 i = e + 1
169 var t: i64 = e
170 if t > s { if b[t - 1] == (13 as u8) { t = t - 1 } } // CRLF tolerated (Windows-authored)
171 let len: i64 = t - s
172 var keep: i64 = 1
173 if len <= 0 { keep = 0 }
174 if keep == 1 { if b[s] == (35 as u8) { keep = 0 } } // '#'
175 if keep == 1 { if out + len + 2 >= icap { keep = 0 } }
176 if keep == 1 {
177 var k: i64 = 0
178 while k < len { ibuf[out + k] = b[s + k]; k = k + 1 }
179 ibuf[out + len] = 0 as u8
180 out = out + len + 1
181 cnt = cnt + 1
182 }
183 }
184 sys_munmap(b, n + 16)
185 pst[7] = ibuf as i64
186 pst[8] = cnt
187 return cnt
188}
189func sgp_is_ignored(name: *u8, pst: *i64) -> i64 {
190 if pst[8] <= 0 { return 0 }
191 let ibuf: *u8 = pst[7] as *u8
192 var idx: i64 = 0
193 var off: i64 = 0
194 while idx < pst[8] {
195 let pat: *u8 = (ibuf as i64 + off) as *u8
196 if sgp_needle_ci(name, pat) == 1 { return 1 }
197 off = off + sgp_slen(pat) + 1
198 idx = idx + 1
199 }
200 return 0
201}
202// ⚠MEASURED 2026-07-20 on the real memory corpus: bare substring needles ATE 17 legitimate knowledge
203// files (feedback-token-economy-ai-layer-cost-axis.md, feedback-no-hardcoded-secrets-config-driven.md,
204// flux-token-taxonomy.md ...). For a BACKUP that is data loss. Key material is never a document, so
205// document/source extensions are exempt from the needle sweep. This does NOT weaken real protection:
206// filename matching never inspected content, so it never protected against a token QUOTED inside a
207// .md anyway -- it only ever guarded key FILES (.key/.pem/.bin/.cap), which stay fully covered.
208func sgp_is_doc_ext(name: *u8) -> i64 {
209 let n: i64 = sgp_slen(name)
210 if n >= 3 { if sgp_needle_ci((name as i64 + n - 3) as *u8, ".md" as *u8) == 1 { return 1 } }
211 if n >= 3 { if sgp_needle_ci((name as i64 + n - 3) as *u8, ".nx" as *u8) == 1 { return 1 } }
212 if n >= 4 { if sgp_needle_ci((name as i64 + n - 4) as *u8, ".css" as *u8) == 1 { return 1 } }
213 if n >= 5 { if sgp_needle_ci((name as i64 + n - 5) as *u8, ".html" as *u8) == 1 { return 1 } }
214 return 0
215}
216func sgp_is_secret(name: *u8) -> i64 {
217 if sgp_is_doc_ext(name) == 1 { return 0 }
218 if sgp_needle_ci(name, "secret" as *u8) == 1 { return 1 }
219 if sgp_needle_ci(name, "passw" as *u8) == 1 { return 1 }
220 if sgp_needle_ci(name, "token" as *u8) == 1 { return 1 }
221 if sgp_needle_ci(name, "credential" as *u8) == 1 { return 1 }
222 if sgp_needle_ci(name, ".pem" as *u8) == 1 { return 1 }
223 if sgp_needle_ci(name, ".cap" as *u8) == 1 { return 1 }
224 if sgp_needle_ci(name, ".key" as *u8) == 1 { return 1 }
225 if sgp_needle_ci(name, "_key" as *u8) == 1 { return 1 }
226 if sgp_needle_ci(name, "_keys" as *u8) == 1 { return 1 }
227 if sgp_needle_ci(name, "privkey" as *u8) == 1 { return 1 }
228 if sgp_needle_ci(name, "private_key" as *u8) == 1 { return 1 }
229 if sgp_needle_ci(name, "id_rsa" as *u8) == 1 { return 1 }
230 if sgp_needle_ci(name, "id_ed25519" as *u8) == 1 { return 1 }
231 if sgp_needle_ci(name, "_seed.bin" as *u8) == 1 { return 1 }
232 if sgp_needle_ci(name, "opaque" as *u8) == 1 { return 1 }
233 return 0
234}
235func sgp_isdots(nm: *u8) -> i64 {
236 if nm[0] == (46 as u8) {
237 if nm[1] == (0 as u8) { return 1 }
238 if nm[1] == (46 as u8) { if nm[2] == (0 as u8) { return 1 } }
239 }
240 return 0
241}
242// path[base] = '/' then name; NUL-terminate; return new length
243func sgp_join(buf: *u8, base: i64, name: *u8) -> i64 {
244 buf[base] = 47 as u8
245 var o: i64 = base + 1
246 var i: i64 = 0
247 while name[i] != (0 as u8) { buf[o] = name[i]; o = o + 1; i = i + 1 }
248 buf[o] = 0 as u8
249 return o
250}
251// little-endian u32 out of a struct stat field
252func sgp_u32(b: *u8, at: i64) -> i64 {
253 return (b[at] as i64) | ((b[at + 1] as i64) << 8) | ((b[at + 2] as i64) << 16) | ((b[at + 3] as i64) << 24)
254}
255// bounded file read (NEVER sys_read_file: its 4 GiB fallback cap fires on every 0-byte file).
256// FAILS LOUD on a short read -- a truncated blob would hash + push as VALID-looking wrong content,
257// the worst failure class. On success out_len[0]==the mapped size, so the caller's munmap is exact.
258func sgp_read_file(path: *u8, out_len: *i64) -> *u8 {
259 out_len[0] = 0
260 let fd: i64 = sys_openat_rd(path)
261 if fd < 0 { return 0 as *u8 }
262 let sz: i64 = sys_lseek(fd, 0, 2)
263 sys_lseek(fd, 0, 0)
264 if sz < 0 { sys_close(fd); return 0 as *u8 }
265 let buf: *u8 = sys_mmap(sz + 16)
266 var tot: i64 = 0
267 var go: i64 = 1
268 while go == 1 {
269 if tot >= sz { go = 0 } else {
270 let n: i64 = sys_read(fd, (buf as i64 + tot) as *u8, sz - tot)
271 if n <= 0 { go = 0 } else { tot = tot + n }
272 }
273 }
274 sys_close(fd)
275 if tot != sz { sys_munmap(buf, sz + 16); return 0 as *u8 }
276 out_len[0] = tot
277 return buf
278}
279
280// ---- packfile (X1b) ----
281func sgp_be32(out: *u8, at: i64, v: i64) -> i64 {
282 out[at] = ((v >> 24) & 255) as u8
283 out[at + 1] = ((v >> 16) & 255) as u8
284 out[at + 2] = ((v >> 8) & 255) as u8
285 out[at + 3] = (v & 255) as u8
286 return at + 4
287}
288func sgp_objhdr(out: *u8, at: i64, typ: i64, size: i64) -> i64 {
289 var a: i64 = at
290 var b: i64 = (typ << 4) | (size & 15)
291 var s: i64 = size >> 4
292 while s > 0 { out[a] = (b | 128) as u8; a = a + 1; b = s & 127; s = s >> 7 }
293 out[a] = b as u8; a = a + 1
294 return a
295}
296// pst: [0]=offset [1]=object count [2]=overflow flag
297func sgp_pack_begin(pack: *u8, pst: *i64) -> i64 {
298 pack[0] = 80 as u8; pack[1] = 65 as u8; pack[2] = 67 as u8; pack[3] = 75 as u8
299 var o: i64 = sgp_be32(pack, 4, 2)
300 o = sgp_be32(pack, o, 0)
301 pst[0] = o
302 pst[1] = 0
303 pst[2] = 0
304 return 0
305}
306// append one object; fixed-Huffman deflate EXPANDS high-entropy data ~1.3x so the guard reserves 1.5x
307func sgp_pack_add(pack: *u8, pst: *i64, typ: i64, content: *u8, clen: i64) -> i64 {
308 if pst[2] != 0 { return 0 - 1 }
309 if pst[0] + clen + (clen >> 1) + 256 > SGP_PACK_CAP - 64 { pst[2] = 1; return 0 - 1 }
310 var a: i64 = sgp_objhdr(pack, pst[0], typ, clen)
311 let z: *u8 = (pack as i64 + a) as *u8
312 let zl: i64 = sg_zwrap(content, clen, z)
313 pst[0] = a + zl
314 pst[1] = pst[1] + 1
315 return 0
316}
317// ---- INCREMENTAL PACKS: a PROVEN-have cache ----
318// Soundness: the remote's ref only advances AFTER `unpack ok`, so if the advert's old ref equals the
319// commit we cached from our last successful push, the remote PROVABLY holds that snapshot's whole
320// object set -- those objects can be skipped. ANY mismatch (someone else pushed, cache lost, repo
321// reset) falls back to a full thick pack. Fail-safe BY CONSTRUCTION: we never skip on an assumption,
322// only on a server-confirmed fact. A wrong skip would produce a repo that clones with "missing blob".
323// Cache file = [32B commit][32B object]*N under knowledge/sovgit_have/.
324func sgp_hs_hash(sha: *u8, mask: i64) -> i64 {
325 var h: i64 = 0
326 var i: i64 = 0
327 while i < 7 { h = (h << 8) | (sha[i] as i64); i = i + 1 } // 7 bytes: never sets the sign bit
328 return h & mask
329}
330func sgp_hs_build(shas: *u8, n: i64, table: *i64, tsize: i64) -> i64 {
331 var i: i64 = 0
332 while i < tsize { table[i] = 0; i = i + 1 }
333 let mask: i64 = tsize - 1
334 i = 0
335 while i < n {
336 let s: *u8 = (shas as i64 + i * 32) as *u8
337 var slot: i64 = sgp_hs_hash(s, mask)
338 var placed: i64 = 0
339 while placed == 0 {
340 if table[slot] == 0 { table[slot] = i + 1; placed = 1 } else { slot = (slot + 1) & mask }
341 }
342 i = i + 1
343 }
344 return 0
345}
346func sgp_hs_has(shas: *u8, table: *i64, tsize: i64, q: *u8) -> i64 {
347 if tsize <= 0 { return 0 }
348 let mask: i64 = tsize - 1
349 var slot: i64 = sgp_hs_hash(q, mask)
350 var res: i64 = 0
351 var go: i64 = 1
352 while go == 1 {
353 let v: i64 = table[slot]
354 if v == 0 { go = 0 } else {
355 let s: *u8 = (shas as i64 + (v - 1) * 32) as *u8
356 var k: i64 = 0
357 while k < 32 { if s[k] != q[k] { k = 40 } else { k = k + 1 } }
358 if k == 32 { res = 1; go = 0 } else { slot = (slot + 1) & mask }
359 }
360 }
361 return res
362}
363// cache path: knowledge/sovgit_have/<repo>_<ref with '/' -> '_'>
364func sgp_havepath(dst: *u8, repo: *u8, ref: *u8) -> i64 {
365 var o: i64 = sgp_cat(dst, 0, "knowledge/sovgit_have/" as *u8)
366 o = sgp_cat(dst, o, repo)
367 dst[o] = 95 as u8; o = o + 1
368 var i: i64 = 0
369 while ref[i] != (0 as u8) {
370 if ref[i] == (47 as u8) { dst[o] = 95 as u8 } else { dst[o] = ref[i] }
371 o = o + 1; i = i + 1
372 }
373 dst[o] = 0 as u8
374 return o
375}
376// record every object of the snapshot, and pack it ONLY if the remote does not provably have it
377func sgp_emit(pack: *u8, pst: *i64, typ: i64, content: *u8, clen: i64, raw32: *u8, allshas: *u8, hshas: *u8, htab: *i64) -> i64 {
378 if pst[3] >= SGP_MAX_OBJ { pst[2] = 1; return 0 - 1 }
379 let d: *u8 = (allshas as i64 + pst[3] * 32) as *u8
380 var i: i64 = 0
381 while i < 32 { d[i] = raw32[i]; i = i + 1 }
382 pst[3] = pst[3] + 1
383 if pst[5] > 0 { if sgp_hs_has(hshas, htab, pst[5], raw32) == 1 { pst[6] = pst[6] + 1; return 0 } }
384 return sgp_pack_add(pack, pst, typ, content, clen)
385}
386func sgp_pack_end(pack: *u8, pst: *i64) -> i64 {
387 sgp_be32(pack, 8, pst[1])
388 let dig: *u8 = sys_mmap(32)
389 sha256_digest(pack, pst[0], dig)
390 var i: i64 = 0
391 while i < 32 { pack[pst[0]] = dig[i]; pst[0] = pst[0] + 1; i = i + 1 }
392 sys_munmap(dig, 32)
393 return pst[0]
394}
395
396// ---- git tree entry ordering: names compare as if a directory carried a trailing '/' ----
397func sgp_chr(s: *u8, n: i64, isdir: i64, i: i64) -> i64 {
398 if i < n { return s[i] as i64 }
399 if i == n { if isdir == 1 { return 47 } }
400 return 0 - 1
401}
402func sgp_keycmp(a: *u8, an: i64, ad: i64, b: *u8, bn: i64, bd: i64) -> i64 {
403 var i: i64 = 0
404 var r: i64 = 0
405 var go: i64 = 1
406 while go == 1 {
407 let ca: i64 = sgp_chr(a, an, ad, i)
408 let cb: i64 = sgp_chr(b, bn, bd, i)
409 if ca < cb { r = 0 - 1; go = 0 } else {
410 if ca > cb { r = 1; go = 0 } else {
411 if ca < 0 { r = 0; go = 0 } else { i = i + 1 }
412 }
413 }
414 }
415 return r
416}
417func sgp_entcmp(arena: *u8, noff: *i64, nlen: *i64, isdir: *i64, x: i64, y: i64) -> i64 {
418 let ax: *u8 = (arena as i64 + noff[x]) as *u8
419 let ay: *u8 = (arena as i64 + noff[y]) as *u8
420 return sgp_keycmp(ax, nlen[x], isdir[x], ay, nlen[y], isdir[y])
421}
422// bottom-up merge sort (O(n log n)): a 15k-file directory makes insertion sort quadratic = minutes
423func sgp_msort(arena: *u8, noff: *i64, nlen: *i64, isdir: *i64, idx: *i64, aux: *i64, n: i64) -> i64 {
424 var width: i64 = 1
425 while width < n {
426 var i: i64 = 0
427 while i < n {
428 var mid: i64 = i + width
429 var hi: i64 = i + width + width
430 if mid > n { mid = n }
431 if hi > n { hi = n }
432 var a: i64 = i
433 var b: i64 = mid
434 var o: i64 = i
435 while o < hi {
436 var take: i64 = 0
437 if a >= mid { take = 1 } else {
438 if b >= hi { take = 0 } else {
439 let c: i64 = sgp_entcmp(arena, noff, nlen, isdir, idx[a], idx[b])
440 if c > 0 { take = 1 } else { take = 0 }
441 }
442 }
443 if take == 1 { aux[o] = idx[b]; b = b + 1 } else { aux[o] = idx[a]; a = a + 1 }
444 o = o + 1
445 }
446 i = i + width + width
447 }
448 var k: i64 = 0
449 while k < n { idx[k] = aux[k]; k = k + 1 }
450 width = width + width
451 }
452 return 0
453}
454
455// ---- the recursive snapshot walk ----
456// stats: [0]=files [1]=dirs [2]=skipped-special [3]=content bytes
457// out_ecount receives this directory's entry count (0 => an empty dir; git cannot track one, so the
458// PARENT omits it, keeping a stock-git round-trip diff clean).
459func sgp_walk(path: *u8, plen: i64, pack: *u8, pst: *i64, depth: i64, out_sha32: *u8, out_ecount: *i64, stats: *i64, allshas: *u8, hshas: *u8, htab: *i64) -> i64 {
460 out_ecount[0] = 0
461 if depth > SGP_MAX_DEPTH { return 0 - 3 }
462 path[plen] = 0 as u8
463 let dfd: i64 = sys_openat_rd(path)
464 if dfd < 0 { return 0 - 4 }
465 let arena: *u8 = sys_mmap(SGP_NAMEARENA)
466 let noff: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64
467 let nlen: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64
468 let isdir: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64
469 let isexe: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64
470 let idx: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64
471 let aux: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64
472 let shas: *u8 = sys_mmap(SGP_MAX_ENT * 32)
473 let dbuf: *u8 = sys_mmap(SGP_MAGIC_131072)
474 let stbuf: *u8 = sys_mmap(160)
475 let szp: *i64 = sys_mmap(16) as *i64
476 let ecb: *i64 = sys_mmap(16) as *i64
477 var nent: i64 = 0
478 var apos: i64 = 0
479 var err: i64 = 0
480 var go: i64 = 1
481 while go == 1 {
482 let nr: i64 = sys_getdents64(dfd, dbuf, SGP_MAGIC_131072)
483 if nr <= 0 { go = 0 } else {
484 var off: i64 = 0
485 while off < nr {
486 let rec: *u8 = (dbuf as i64 + off) as *u8
487 let nm: *u8 = dirent_name(rec)
488 var skip: i64 = 0
489 if err != 0 { skip = 1 }
490 if sgp_isdots(nm) == 1 { skip = 1 }
491 if sgp_streq(nm, ".git" as *u8) == 1 { skip = 1 }
492 if skip == 0 { if sgp_is_ignored(nm, pst) == 1 {
493 skip = 1
494 pst[9] = pst[9] + 1
495 sgp_we("SKIP-IGNORED " as *u8); sgp_we(nm); sgp_we("\n" as *u8)
496 } }
497 if skip == 0 { if sgp_is_secret(nm) == 1 {
498 skip = 1
499 stats[4] = stats[4] + 1
500 sgp_we("SKIP-SECRET " as *u8); sgp_we(nm); sgp_we("\n" as *u8)
501 } }
502 if skip == 0 {
503 let nl: i64 = sgp_slen(nm)
504 if nent >= SGP_MAX_ENT { err = 0 - 5 } else {
505 if apos + nl + 2 >= SGP_NAMEARENA { err = 0 - 6 } else {
506 if plen + nl + 2 >= SGP_PATHBUF { err = 0 - 7 } else {
507 let cl: i64 = sgp_join(path, plen, nm)
508 var keep: i64 = 0
509 var kdir: i64 = 0
510 var kexe: i64 = 0
511 let csha: *u8 = (shas as i64 + nent * 32) as *u8
512 if sys_fstatat(path, stbuf) < 0 { stats[2] = stats[2] + 1 } else {
513 let mode: i64 = sgp_u32(stbuf, 24)
514 let fmt: i64 = mode & SGP_MAGIC_61440
515 if fmt == SGP_MAGIC_16384 {
516 let rr: i64 = sgp_walk(path, cl, pack, pst, depth + 1, csha, ecb, stats, allshas, hshas, htab)
517 if rr != 0 { err = rr } else {
518 if ecb[0] > 0 { keep = 1; kdir = 1; stats[1] = stats[1] + 1 }
519 }
520 } else {
521 if fmt == SGP_MAGIC_32768 {
522 let content: *u8 = sgp_read_file(path, szp)
523 if (content as i64) == 0 { err = 0 - 8 } else {
524 let clen: i64 = szp[0]
525 if clen > SGP_MAX_FILE { err = 0 - 9 } else {
526 sg_oid_raw("blob" as *u8, content, clen, csha)
527 if sgp_emit(pack, pst, 3, content, clen, csha, allshas, hshas, htab) != 0 { err = 0 - 10 } else {
528 keep = 1
529 if (mode & 73) != 0 { kexe = 1 }
530 stats[0] = stats[0] + 1
531 stats[3] = stats[3] + clen
532 }
533 }
534 sys_munmap(content, szp[0] + 16)
535 }
536 } else { stats[2] = stats[2] + 1 }
537 }
538 }
539 if keep == 1 {
540 noff[nent] = apos
541 nlen[nent] = nl
542 var k: i64 = 0
543 while k < nl { arena[apos + k] = nm[k]; k = k + 1 }
544 arena[apos + nl] = 0 as u8
545 apos = apos + nl + 1
546 isdir[nent] = kdir
547 isexe[nent] = kexe
548 nent = nent + 1
549 }
550 } } }
551 }
552 off = off + dirent_reclen(rec)
553 }
554 }
555 }
556 sys_close(dfd)
557 if err == 0 {
558 var i: i64 = 0
559 while i < nent { idx[i] = i; i = i + 1 }
560 sgp_msort(arena, noff, nlen, isdir, idx, aux, nent)
561 var tcap: i64 = 128
562 i = 0
563 while i < nent { tcap = tcap + nlen[i] + 48; i = i + 1 }
564 let tree: *u8 = sys_mmap(tcap)
565 var to: i64 = 0
566 i = 0
567 while i < nent {
568 let e: i64 = idx[i]
569 let nmp: *u8 = (arena as i64 + noff[e]) as *u8
570 var modestr: *u8 = "100644" as *u8
571 if isdir[e] == 1 { modestr = "40000" as *u8 } else { if isexe[e] == 1 { modestr = "100755" as *u8 } }
572 let rawp: *u8 = (shas as i64 + e * 32) as *u8
573 to = sg_tree_entry(tree, to, modestr, nmp, rawp)
574 i = i + 1
575 }
576 sg_oid_raw("tree" as *u8, tree, to, out_sha32)
577 if sgp_emit(pack, pst, 2, tree, to, out_sha32, allshas, hshas, htab) != 0 { err = 0 - 10 }
578 sys_munmap(tree, tcap)
579 out_ecount[0] = nent
580 }
581 sys_munmap(arena, SGP_NAMEARENA)
582 sys_munmap(noff as *u8, SGP_MAX_ENT * 8)
583 sys_munmap(nlen as *u8, SGP_MAX_ENT * 8)
584 sys_munmap(isdir as *u8, SGP_MAX_ENT * 8)
585 sys_munmap(isexe as *u8, SGP_MAX_ENT * 8)
586 sys_munmap(idx as *u8, SGP_MAX_ENT * 8)
587 sys_munmap(aux as *u8, SGP_MAX_ENT * 8)
588 sys_munmap(shas, SGP_MAX_ENT * 32)
589 sys_munmap(dbuf, SGP_MAGIC_131072)
590 sys_munmap(stbuf, 160)
591 sys_munmap(szp as *u8, 16)
592 sys_munmap(ecb as *u8, 16)
593 return err
594}
595
596// ---- loopback HTTP transport (the daemon is wall-only: 127.0.0.1 by construction) ----
597func sgp_connect(port: i64) -> i64 {
598 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
599 if fd < 0 { return 0 - 1 }
600 let sa: *u8 = sys_mmap(16)
601 var i: i64 = 0
602 while i < 16 { sa[i] = 0 as u8; i = i + 1 }
603 sa[0] = 2 as u8
604 sa[2] = ((port >> 8) & 255) as u8
605 sa[3] = (port & 255) as u8
606 sa[4] = 127 as u8; sa[5] = 0 as u8; sa[6] = 0 as u8; sa[7] = 1 as u8
607 let cr: i64 = nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS)
608 sys_munmap(sa, 16)
609 if cr < 0 { sys_close(fd); return 0 - 2 }
610 return fd
611}
612func sgp_write_all(fd: i64, buf: *u8, n: i64) -> i64 {
613 var off: i64 = 0
614 var bad: i64 = 0
615 while off < n {
616 let w: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off)
617 if w <= 0 { off = n; bad = 1 } else { off = off + w }
618 }
619 return bad
620}
621func sgp_drain(fd: i64, buf: *u8, cap: i64) -> i64 {
622 var off: i64 = 0
623 var go: i64 = 1
624 while go == 1 {
625 if off >= cap { go = 0 } else {
626 let r: i64 = sys_read(fd, (buf as i64 + off) as *u8, cap - off)
627 if r <= 0 { go = 0 } else { off = off + r }
628 }
629 }
630 return off
631}
632// build an HTTP/1.1 request; capstr (may be 0) rides as X-Nishi-Cap (the API wire form the login gate accepts)
633func sgp_build_req(out: *u8, method: *u8, path: *u8, host: *u8, capstr: *u8, ct: *u8, body: *u8, blen: i64) -> i64 {
634 var o: i64 = sgp_cat(out, 0, method)
635 out[o] = 32 as u8; o = o + 1
636 o = sgp_cat(out, o, path)
637 o = sgp_cat(out, o, " HTTP/1.1\r\nHost: " as *u8)
638 o = sgp_cat(out, o, host)
639 o = sgp_cat(out, o, "\r\n" as *u8)
640 if (capstr as i64) != 0 {
641 o = sgp_cat(out, o, "X-Nishi-Cap: " as *u8)
642 o = sgp_cat(out, o, capstr)
643 o = sgp_cat(out, o, "\r\n" as *u8)
644 }
645 if blen > 0 {
646 o = sgp_cat(out, o, "Content-Type: " as *u8)
647 o = sgp_cat(out, o, ct)
648 o = sgp_cat(out, o, "\r\nContent-Length: " as *u8)
649 o = sgp_dec(out, o, blen)
650 o = sgp_cat(out, o, "\r\n" as *u8)
651 }
652 o = sgp_cat(out, o, "Connection: close\r\n\r\n" as *u8)
653 var i: i64 = 0
654 while i < blen { out[o + i] = body[i]; i = i + 1 }
655 return o + blen
656}
657func sgp_http(port: i64, req: *u8, reqlen: i64, out: *u8, outcap: i64) -> i64 {
658 let fd: i64 = sgp_connect(port)
659 if fd < 0 { return 0 - 1 }
660 if sgp_write_all(fd, req, reqlen) != 0 { sys_close(fd); return 0 - 2 }
661 let n: i64 = sgp_drain(fd, out, outcap)
662 sys_close(fd)
663 return n
664}
665
666// ---- TLS transport (rung 2): identical request bytes, sovereign TLS-1.3 ----
667// Mirrors the PROVEN nx_mgmt_client send/drain (nx_mgmt_client.nx:220-299): fragment the request into
668// <=16KB records (RFC 8446 max plaintext 2^14 -- a single oversized record is protocol-invalid and was
669// the historic transport rc=-2 root), then decrypt-drain until the peer closes.
670func sgp_tls_send_drain(s: *Tls13ClientSession, fd: i64, req: *u8, req_len: i64, out: *u8, out_cap: i64) -> i64 {
671 if s.state != NX_TLS13_CSESSION_STATE_CONNECTED { return 0 - 1 }
672 let rec_buf: *u8 = sys_mmap(SGP_MAGIC_16384 + 64)
673 var snd_off: i64 = 0
674 var more: i64 = 1
675 var senderr: i64 = 0
676 while more == 1 { more = 0
677 var frag: i64 = req_len - snd_off
678 if frag > SGP_MAGIC_16384 { frag = SGP_MAGIC_16384 }
679 let header_out: *u8 = rec_buf
680 let ct_out: *u8 = ((rec_buf as i64) + NX_TLS13_RECORD_HEADER_LEN) as *u8
681 let tag_out: *u8 = ((rec_buf as i64) + NX_TLS13_RECORD_HEADER_LEN + frag + 1) as *u8
682 let enc_v: i64 = nx_tls13_record_encrypt_v2(s.cipher_suite, s.client_app_traffic_key, s.client_app_iv, s.client_app_seq, ((req as i64) + snd_off) as *u8, frag, NX_TLS13_CT_APPLICATION_DATA, 0, header_out, ct_out, tag_out)
683 s.client_app_seq = s.client_app_seq + 1
684 if enc_v != NX_TLS13_REC_VERDICT_OK { senderr = 0 - 2 } else {
685 let total: i64 = NX_TLS13_RECORD_HEADER_LEN + frag + 1 + NX_TLS13_RECORD_TAG_LEN
686 if sgp_write_all(fd, rec_buf, total) != 0 { senderr = 0 - 3 } else {
687 snd_off = snd_off + frag
688 if snd_off < req_len { more = 1 }
689 }
690 }
691 }
692 sys_munmap(rec_buf, SGP_MAGIC_16384 + 64)
693 if senderr != 0 { return senderr }
694 var acc: i64 = 0
695 let rec_in: *u8 = sys_mmap(SGP_MAGIC_16645)
696 let plain: *u8 = sys_mmap(SGP_MAGIC_16645)
697 let cttype: *i64 = sys_mmap(16) as *i64
698 let ptlen: *i64 = sys_mmap(16) as *i64
699 var go: i64 = 1
700 while go == 1 {
701 if acc >= out_cap { go = 0 } else {
702 let rin: i64 = nx_tls13_read_record_from_fd(fd, rec_in, SGP_MAGIC_16645)
703 if rin < 0 { go = 0 } else {
704 let ctlen: i64 = rin - NX_TLS13_RECORD_HEADER_LEN - NX_TLS13_RECORD_TAG_LEN
705 let rin_ct: *u8 = ((rec_in as i64) + NX_TLS13_RECORD_HEADER_LEN) as *u8
706 let rin_tag: *u8 = ((rec_in as i64) + rin - NX_TLS13_RECORD_TAG_LEN) as *u8
707 let dv: i64 = nx_tls13_record_decrypt_v2(s.cipher_suite, s.server_app_traffic_key, s.server_app_iv, s.server_app_seq, rec_in, rin_ct, ctlen, rin_tag, plain, cttype, ptlen)
708 s.server_app_seq = s.server_app_seq + 1
709 if dv != NX_TLS13_REC_VERDICT_OK { go = 0 } else {
710 if cttype[0] == NX_TLS13_CT_ALERT { go = 0 } else {
711 if cttype[0] == NX_TLS13_CT_APPLICATION_DATA {
712 let tc: i64 = ptlen[0]
713 if acc + tc > out_cap { go = 0 } else {
714 var i: i64 = 0
715 while i < tc { out[acc + i] = plain[i]; i = i + 1 }
716 acc = acc + tc
717 }
718 }
719 }
720 }
721 }
722 }
723 }
724 sys_munmap(rec_in, SGP_MAGIC_16645)
725 sys_munmap(plain, SGP_MAGIC_16645)
726 sys_munmap(cttype as *u8, 16)
727 sys_munmap(ptlen as *u8, 16)
728 return acc
729}
730// connect + validated TLS-1.3 handshake to `url`'s host:port, send req, drain the response
731func sgp_https(store: *TrustStore, url: *u8, req: *u8, reqlen: i64, out: *u8, outcap: i64) -> i64 {
732 let url_p: *NxUrl = nx_url_new()
733 let target_raw: *u8 = sys_mmap(64)
734 let target: *NxHttpsTarget = target_raw as *NxHttpsTarget
735 target.url = url_p
736 target.port = 0
737 if nx_https_url_for_fetch(url, target) != NX_HTTPS_URL_OK { return 0 - 10 }
738 let now: i64 = sys_now_realtime_sec()
739 let fd_p: *i64 = sys_mmap(16) as *i64
740 if nx_https_url_connect(target, url, now, fd_p) != NX_HTTPS_CONNECT_OK { return 0 - 11 }
741 let fd: i64 = fd_p[0]
742 let host: *u8 = ((url as i64) + target.url.host_off) as *u8
743 let hlen: i64 = target.url.host_len
744 let cr: *u8 = sys_mmap(32)
745 let priv: *u8 = sys_mmap(32)
746 nx_csprng_fill(cr, 32) // fresh ephemerals -> forward secrecy per push
747 nx_csprng_fill(priv, 32)
748 let vc: *TlsValidationContext = sys_mmap(128) as *TlsValidationContext
749 vc.store = store
750 vc.sni_host = host
751 vc.sni_host_len = hlen
752 vc.now_epoch = now
753 let sr: i64 = nx_tls13_client_session_run(fd, host, hlen, cr, priv, vc)
754 if sr <= 0 { sys_close(fd); return 0 - 12 }
755 let session: *Tls13ClientSession = sr as *Tls13ClientSession
756 let n: i64 = sgp_tls_send_drain(session, fd, req, reqlen, out, outcap)
757 sys_close(fd)
758 return n
759}
760// one dispatcher, two transports: mode 0 = loopback plain TCP, mode 1 = sovereign TLS to urlbase+path
761func sgp_xfer_once(mode: i64, port: i64, store: *TrustStore, urlbase: *u8, path: *u8, req: *u8, reqlen: i64, out: *u8, outcap: i64) -> i64 {
762 if mode == 0 { return sgp_http(port, req, reqlen, out, outcap) }
763 let url: *u8 = sys_mmap(SGP_MAGIC_2048)
764 var u: i64 = sgp_cat(url, 0, urlbase)
765 u = sgp_cat(url, u, path)
766 url[u] = 0 as u8
767 let n: i64 = sgp_https(store, url, req, reqlen, out, outcap)
768 sys_munmap(url, SGP_MAGIC_2048)
769 return n
770}
771// BOUNDED RETRY on TRANSPORT failure only (rc<=0 = we never got a response). An unattended 30-min ark
772// beat must survive the known flake classes (transient TLS handshake/cert-verify, the DSM co-squat).
773// SAFE for BOTH calls: the advert is an idempotent read, and a receive-pack retry replays the SAME
774// (old,new,ref) command -- the server writes the same ref value, so a lost response cannot double-apply.
775// A SERVER answer (any bytes) is never retried -- protocol/auth rejections must surface, not be masked.
776func sgp_xfer(mode: i64, port: i64, store: *TrustStore, urlbase: *u8, path: *u8, req: *u8, reqlen: i64, out: *u8, outcap: i64) -> i64 {
777 var attempt: i64 = 0
778 var n: i64 = 0 - 1
779 var go: i64 = 1
780 while go == 1 {
781 n = sgp_xfer_once(mode, port, store, urlbase, path, req, reqlen, out, outcap)
782 if n > 0 { go = 0 } else {
783 attempt = attempt + 1
784 if attempt >= 3 { go = 0 } else {
785 sgp_we("RETRY transport rc=" as *u8)
786 let b: *u8 = sys_mmap(32)
787 var x: i64 = 0 - n
788 var i: i64 = 31
789 if x == 0 { b[i] = 48 as u8; i = i - 1 }
790 while x > 0 { b[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 }
791 sys_write(2, (b as i64 + i + 1) as *u8, 31 - i)
792 sys_munmap(b, 32)
793 sgp_we(" (transient; the response never arrived) attempt " as *u8)
794 let c: *u8 = sys_mmap(8)
795 c[0] = (48 + attempt) as u8
796 sys_write(2, c, 1)
797 sys_munmap(c, 8)
798 sgp_we("/3\n" as *u8)
799 }
800 }
801 }
802 return n
803}
804// bounded diagnostic dump (never reads past what the peer actually sent)
805func sgp_dump(buf: *u8, n: i64, want: i64) -> i64 {
806 var m: i64 = want
807 if n < m { m = n }
808 if m > 0 { sys_write(1, buf, m) }
809 return 0
810}
811// substring search
812func sgp_find(hay: *u8, hn: i64, needle: *u8) -> i64 {
813 let m: i64 = sgp_slen(needle)
814 if m == 0 { return 0 }
815 var i: i64 = 0
816 while i + m <= hn {
817 var k: i64 = 0
818 while k < m { if hay[i + k] != needle[k] { k = m + 9 } else { k = k + 1 } }
819 if k == m { return i }
820 i = i + 1
821 }
822 return 0 - 1
823}
824// find "<64hex> refs/heads/<ref>" in a receive-pack advert; writes the 64 hex to out64. 1 = found.
825func sgp_find_ref(buf: *u8, n: i64, refpath: *u8, out64: *u8) -> i64 {
826 let needle: *u8 = sys_mmap(320)
827 var o: i64 = sgp_cat(needle, 0, " " as *u8)
828 o = sgp_cat(needle, o, refpath)
829 needle[o] = 0 as u8
830 let nl: i64 = o
831 var found: i64 = 0
832 var i: i64 = 0
833 while i + nl <= n {
834 var k: i64 = 0
835 while k < nl { if buf[i + k] != needle[k] { k = nl + 9 } else { k = k + 1 } }
836 if k == nl {
837 if i >= 64 {
838 let after: i64 = buf[i + nl] as i64
839 var okend: i64 = 0
840 if after == 0 { okend = 1 }
841 if after == 10 { okend = 1 }
842 if okend == 1 {
843 var j: i64 = 0
844 while j < 64 { out64[j] = buf[i - 64 + j]; j = j + 1 }
845 out64[64] = 0 as u8
846 found = 1
847 i = n
848 }
849 }
850 }
851 i = i + 1
852 }
853 sys_munmap(needle, 320)
854 return found
855}
856
857func main(argc: i64, argv: *i64) -> i64 {
858 if argc < 3 {
859 sgp_w("usage: nx_sovgit_push <srcdir> <repo> [<port>|<host>:<port>] [ref] [capfile] [message]\n" as *u8)
860 sgp_w(" target '18691' = loopback plain TCP (on-box) | 'nishifamily.com:8443' = sovereign TLS-1.3\n" as *u8)
861 sgp_w(" ENVELOPE entries/dir<=20000 namebytes/dir<=2MiB depth<=32 file<=64MiB pack<=128MiB\n" as *u8)
862 sgp_w(" skips .git + symlinks/special (counted); transport=loopback 127.0.0.1:<port> (default 18691)\n" as *u8)
863 return 2
864 }
865 let srcdir: *u8 = argv[1] as *u8
866 let repo: *u8 = argv[2] as *u8
867 // target: "<port>" = loopback plain TCP (on-box) | "<host>:<port>" = sovereign TLS through the edge
868 var port: i64 = SGP_MAGIC_18691
869 var mode: i64 = 0
870 let hostbuf: *u8 = sys_mmap(320)
871 var hb: i64 = sgp_cat(hostbuf, 0, "nishi-git" as *u8)
872 hostbuf[hb] = 0 as u8
873 if argc > 3 {
874 let t: *u8 = argv[3] as *u8
875 var ci: i64 = 0 - 1
876 var ti: i64 = 0
877 while t[ti] != (0 as u8) { if t[ti] == (58 as u8) { ci = ti } ti = ti + 1 }
878 if ci < 0 { port = sgp_atoi(t) } else {
879 mode = 1
880 var k: i64 = 0
881 while k < ci { hostbuf[k] = t[k]; k = k + 1 }
882 hostbuf[ci] = 0 as u8
883 port = sgp_atoi((t as i64 + ci + 1) as *u8)
884 }
885 }
886 var store: *TrustStore = 0 as *TrustStore
887 let urlbase: *u8 = sys_mmap(512)
888 urlbase[0] = 0 as u8
889 if mode == 1 {
890 let tr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, SGP_MAGIC_4194304)
891 if tr <= 0 { sgp_w("PUSH-FAIL trust-store load (run from the nxc2 dir) verdict=RED\n" as *u8); sys_exit(10); return 10 }
892 store = tr as *TrustStore
893 var ub: i64 = sgp_cat(urlbase, 0, "https://" as *u8)
894 ub = sgp_cat(urlbase, ub, hostbuf)
895 urlbase[ub] = 58 as u8; ub = ub + 1
896 ub = sgp_dec(urlbase, ub, port)
897 urlbase[ub] = 0 as u8
898 }
899 var ref: *u8 = "master" as *u8
900 if argc > 4 { ref = argv[4] as *u8 }
901 var capstr: *u8 = 0 as *u8
902 if argc > 5 {
903 let cszp: *i64 = sys_mmap(16) as *i64
904 let cb: *u8 = sgp_read_file(argv[5] as *u8, cszp)
905 if (cb as i64) != 0 {
906 var cn: i64 = cszp[0]
907 while cn > 0 {
908 let lc: i64 = cb[cn - 1] as i64
909 if lc == 10 { cn = cn - 1 } else { if lc == 13 { cn = cn - 1 } else { cn = 0 - cn } }
910 }
911 if cn < 0 { cn = 0 - cn }
912 cb[cn] = 0 as u8
913 if cn > 0 { capstr = cb }
914 }
915 }
916 var msg: *u8 = "nishi sovereign snapshot" as *u8
917 if argc > 6 { msg = argv[6] as *u8 }
918
919 let refpath: *u8 = sys_mmap(320)
920 var rpo: i64 = sgp_cat(refpath, 0, "refs/heads/" as *u8)
921 rpo = sgp_cat(refpath, rpo, ref)
922 refpath[rpo] = 0 as u8
923
924 sgp_w("SOVGIT-PUSH src=" as *u8); sgp_w(srcdir)
925 sgp_w(" repo=" as *u8); sgp_w(repo)
926 sgp_w(" ref=" as *u8); sgp_w(refpath)
927 if mode == 1 { sgp_w(" transport=TLS host=" as *u8); sgp_w(hostbuf) } else { sgp_w(" transport=loopback" as *u8) }
928 sgp_w(" port=" as *u8); sgp_wn(port); sgp_w("\n" as *u8)
929
930 // ---- 1. receive-pack advert -> the old ref value (parent) ----
931 let resp: *u8 = sys_mmap(SGP_RESP)
932 let req0: *u8 = sys_mmap(SGP_MAGIC_8192)
933 let apath: *u8 = sys_mmap(SGP_MAGIC_1024)
934 var ao: i64 = sgp_cat(apath, 0, "/git/" as *u8)
935 ao = sgp_cat(apath, ao, repo)
936 ao = sgp_cat(apath, ao, "/info/refs?service=git-receive-pack" as *u8)
937 apath[ao] = 0 as u8
938 let r0len: i64 = sgp_build_req(req0, "GET" as *u8, apath, hostbuf, capstr, "" as *u8, 0 as *u8, 0)
939 let an: i64 = sgp_xfer(mode, port, store, urlbase, apath, req0, r0len, resp, SGP_RESP)
940 if an <= 0 { sgp_w("PUSH-FAIL advert transport rc=" as *u8); sgp_wn(an); sgp_w(" verdict=RED\n" as *u8); sys_exit(3); return 3 }
941 if sgp_find(resp, an, "200 OK" as *u8) < 0 {
942 sgp_w("PUSH-FAIL advert status (auth? repo?) verdict=RED\n" as *u8)
943 sgp_dump(resp, an, 200)
944 sgp_w("\n" as *u8)
945 sys_exit(4); return 4
946 }
947 let oldsha: *u8 = sys_mmap(80)
948 let hasold: i64 = sgp_find_ref(resp, an, refpath, oldsha)
949 if hasold == 0 { var z: i64 = 0; while z < 64 { oldsha[z] = 48 as u8; z = z + 1 } oldsha[64] = 0 as u8 }
950 sgp_w(" old=" as *u8); sgp_w(oldsha); sgp_w("\n" as *u8)
951
952 // ---- 2. snapshot the tree into a packfile ----
953 let pack: *u8 = sys_mmap(SGP_PACK_CAP)
954 let pst: *i64 = sys_mmap(128) as *i64
955 sgp_pack_begin(pack, pst)
956 let ibuf: *u8 = sys_mmap(SGP_MAGIC_65536)
957 sgp_load_ignore(srcdir, ibuf, SGP_MAGIC_65536, pst)
958 if pst[8] > 0 { sgp_w(" .sovgitignore: " as *u8); sgp_wn(pst[8]); sgp_w(" patterns\n" as *u8) }
959 let stats: *i64 = sys_mmap(64) as *i64
960 stats[0] = 0; stats[1] = 0; stats[2] = 0; stats[3] = 0; stats[4] = 0
961 // ---- proven-have cache: skip objects the remote is CONFIRMED to hold (else full pack) ----
962 let allshas: *u8 = sys_mmap(SGP_MAX_OBJ * 32)
963 let hshas: *u8 = sys_mmap(SGP_MAX_OBJ * 32)
964 let htab: *i64 = sys_mmap(SGP_HS_SIZE * 8) as *i64
965 pst[3] = 0 // objects recorded this snapshot
966 pst[5] = 0 // hash-table size (0 = no have-set => full pack)
967 pst[6] = 0 // objects skipped as provably-present
968 let hpath: *u8 = sys_mmap(SGP_MAGIC_1024)
969 sgp_havepath(hpath, repo, refpath)
970 var haven: i64 = 0
971 if hasold == 1 {
972 let hszp: *i64 = sys_mmap(16) as *i64
973 let hbuf: *u8 = sgp_read_file(hpath, hszp)
974 if (hbuf as i64) != 0 {
975 let hn: i64 = hszp[0]
976 if hn >= 32 {
977 let chex: *u8 = sys_mmap(80)
978 sg_hex(hbuf, chex)
979 chex[64] = 0 as u8
980 if sgp_streq(chex, oldsha) == 1 { // the advert CONFIRMS this snapshot landed
981 var cnt: i64 = (hn - 32) / 32
982 if cnt > SGP_MAX_OBJ { cnt = SGP_MAX_OBJ }
983 var ci: i64 = 0
984 while ci < cnt * 32 { hshas[ci] = hbuf[32 + ci]; ci = ci + 1 }
985 haven = cnt
986 }
987 sys_munmap(chex, 80)
988 }
989 sys_munmap(hbuf, hn + 16)
990 }
991 sys_munmap(hszp as *u8, 16)
992 }
993 if haven > 0 {
994 sgp_hs_build(hshas, haven, htab, SGP_HS_SIZE)
995 pst[5] = SGP_HS_SIZE
996 sgp_w(" incremental: remote CONFIRMED to hold " as *u8); sgp_wn(haven); sgp_w(" objects (delta pack)\n" as *u8)
997 } else { sgp_w(" incremental: no confirmed have-set for this ref (FULL pack, fail-safe)\n" as *u8) }
998 let pathbuf: *u8 = sys_mmap(SGP_PATHBUF)
999 var pl: i64 = sgp_cat(pathbuf, 0, srcdir)
1000 while pl > 1 { if pathbuf[pl - 1] == (47 as u8) { pl = pl - 1 } else { pl = 0 - pl } }
1001 if pl < 0 { pl = 0 - pl }
1002 pathbuf[pl] = 0 as u8
1003 let troot: *u8 = sys_mmap(40)
1004 let ecb: *i64 = sys_mmap(16) as *i64
1005 let wr: i64 = sgp_walk(pathbuf, pl, pack, pst, 0, troot, ecb, stats, allshas, hshas, htab)
1006 if wr != 0 {
1007 sgp_w("PUSH-FAIL walk rc=" as *u8); sgp_wn(wr)
1008 sgp_w(" (-3 depth -4 opendir -5 entries/dir -6 namebytes -7 pathlen -8 read -9 filesize -10 pack-envelope)" as *u8)
1009 sgp_w(" verdict=RED\n" as *u8)
1010 sys_exit(5); return 5
1011 }
1012 let trhex: *u8 = sys_mmap(80)
1013 sg_hex(troot, trhex)
1014 trhex[64] = 0 as u8
1015
1016 // ---- 3. the commit (parent = advertised old ref => additive history) ----
1017 let cbody: *u8 = sys_mmap(SGP_MAGIC_4096)
1018 var co: i64 = sgp_cat(cbody, 0, "tree " as *u8)
1019 co = sgp_cat(cbody, co, trhex)
1020 cbody[co] = 10 as u8; co = co + 1
1021 if hasold == 1 {
1022 co = sgp_cat(cbody, co, "parent " as *u8)
1023 co = sgp_cat(cbody, co, oldsha)
1024 cbody[co] = 10 as u8; co = co + 1
1025 }
1026 let now: i64 = sys_now_realtime_sec()
1027 co = sgp_cat(cbody, co, "author nishi-sovgit <ark@nishi> " as *u8)
1028 co = sgp_dec(cbody, co, now)
1029 co = sgp_cat(cbody, co, " +0000\n" as *u8)
1030 co = sgp_cat(cbody, co, "committer nishi-sovgit <ark@nishi> " as *u8)
1031 co = sgp_dec(cbody, co, now)
1032 co = sgp_cat(cbody, co, " +0000\n\n" as *u8)
1033 co = sgp_cat(cbody, co, msg)
1034 cbody[co] = 10 as u8; co = co + 1
1035 let craw: *u8 = sys_mmap(40)
1036 sg_oid_raw("commit" as *u8, cbody, co, craw)
1037 let newsha: *u8 = sys_mmap(80)
1038 sg_hex(craw, newsha)
1039 newsha[64] = 0 as u8
1040 if sgp_emit(pack, pst, 1, cbody, co, craw, allshas, hshas, htab) != 0 {
1041 sgp_w("PUSH-FAIL pack/object envelope exceeded verdict=RED\n" as *u8); sys_exit(6); return 6
1042 }
1043 let packlen: i64 = sgp_pack_end(pack, pst)
1044 sgp_w(" snapshot files=" as *u8); sgp_wn(stats[0])
1045 sgp_w(" dirs=" as *u8); sgp_wn(stats[1])
1046 sgp_w(" skipped=" as *u8); sgp_wn(stats[2])
1047 sgp_w(" secrets-excluded=" as *u8); sgp_wn(stats[4])
1048 sgp_w(" ignored=" as *u8); sgp_wn(pst[9])
1049 sgp_w(" bytes=" as *u8); sgp_wn(stats[3])
1050 sgp_w(" snapshot-objects=" as *u8); sgp_wn(pst[3])
1051 sgp_w(" packed=" as *u8); sgp_wn(pst[1])
1052 sgp_w(" skipped-present=" as *u8); sgp_wn(pst[6])
1053 sgp_w(" pack=" as *u8); sgp_wn(packlen); sgp_w("\n" as *u8)
1054 sgp_w(" tree=" as *u8); sgp_w(trhex); sgp_w("\n" as *u8)
1055 sgp_w(" new=" as *u8); sgp_w(newsha); sgp_w("\n" as *u8)
1056
1057 // ---- 4. receive-pack: pkt-line ref-update + flush + pack ----
1058 let body: *u8 = sys_mmap(SGP_PACK_CAP + SGP_MAGIC_65536)
1059 let cmd: *u8 = sys_mmap(SGP_MAGIC_1024)
1060 var mo: i64 = sgp_cat(cmd, 0, oldsha)
1061 cmd[mo] = 32 as u8; mo = mo + 1
1062 mo = sgp_cat(cmd, mo, newsha)
1063 cmd[mo] = 32 as u8; mo = mo + 1
1064 mo = sgp_cat(cmd, mo, refpath)
1065 cmd[mo] = 0 as u8; mo = mo + 1
1066 mo = sgp_cat(cmd, mo, "report-status object-format=sha256 agent=nishi-sovgit-push/1" as *u8)
1067 cmd[mo] = 10 as u8; mo = mo + 1
1068 // pkt-line: 4 hex length (payload + 4) then payload
1069 let tl: i64 = mo + 4
1070 var bo: i64 = 0
1071 var hx: i64 = 0
1072 while hx < 4 {
1073 let nib: i64 = (tl >> ((3 - hx) * 4)) & 15
1074 var ch: i64 = 48 + nib
1075 if nib > 9 { ch = 87 + nib }
1076 body[bo] = ch as u8; bo = bo + 1
1077 hx = hx + 1
1078 }
1079 var ci: i64 = 0
1080 while ci < mo { body[bo] = cmd[ci]; bo = bo + 1; ci = ci + 1 }
1081 body[bo] = 48 as u8; body[bo + 1] = 48 as u8; body[bo + 2] = 48 as u8; body[bo + 3] = 48 as u8
1082 bo = bo + 4
1083 var pi: i64 = 0
1084 while pi < packlen { body[bo + pi] = pack[pi]; pi = pi + 1 }
1085 bo = bo + packlen
1086
1087 // refuse-before-send: never learn the edge's limit by taking the public web surface down
1088 if mode == 1 { if bo > SGP_EDGE_SAFE {
1089 sgp_w("PUSH-REFUSED body=" as *u8); sgp_wn(bo)
1090 sgp_w("B exceeds the MEASURED edge-safe cap " as *u8); sgp_wn(SGP_EDGE_SAFE)
1091 sgp_w("B (seq334: oversized POSTs crash-loop sites.elf and down every site).\n" as *u8)
1092 sgp_w(" push a SMALLER SCOPE (fewer files per push -- incremental packs make batching cheap)," as *u8)
1093 sgp_w(" or run on-box against loopback where no edge is in the path. verdict=RED\n" as *u8)
1094 sys_exit(11); return 11
1095 } }
1096 let ppath: *u8 = sys_mmap(SGP_MAGIC_1024)
1097 var po: i64 = sgp_cat(ppath, 0, "/git/" as *u8)
1098 po = sgp_cat(ppath, po, repo)
1099 po = sgp_cat(ppath, po, "/git-receive-pack" as *u8)
1100 ppath[po] = 0 as u8
1101 let req1: *u8 = sys_mmap(SGP_PACK_CAP + SGP_MAGIC_131072)
1102 let r1len: i64 = sgp_build_req(req1, "POST" as *u8, ppath, hostbuf, capstr, "application/x-git-receive-pack-request" as *u8, body, bo)
1103 let pn: i64 = sgp_xfer(mode, port, store, urlbase, ppath, req1, r1len, resp, SGP_RESP)
1104 if pn <= 0 { sgp_w("PUSH-FAIL receive-pack transport rc=" as *u8); sgp_wn(pn); sgp_w(" verdict=RED\n" as *u8); sys_exit(7); return 7 }
1105
1106 // ---- 5. report-status ----
1107 var okline: *u8 = sys_mmap(320)
1108 var oo: i64 = sgp_cat(okline, 0, "ok " as *u8)
1109 oo = sgp_cat(okline, oo, refpath)
1110 okline[oo] = 0 as u8
1111 let unpackok: i64 = sgp_find(resp, pn, "unpack ok" as *u8)
1112 let refok: i64 = sgp_find(resp, pn, okline)
1113 if unpackok < 0 {
1114 sgp_w("PUSH-FAIL server reported unpack failure verdict=RED\n" as *u8)
1115 sgp_dump(resp, pn, 400); sgp_w("\n" as *u8)
1116 sys_exit(8); return 8
1117 }
1118 if refok < 0 {
1119 sgp_w("PUSH-FAIL ref not updated (ng) verdict=RED\n" as *u8)
1120 sgp_dump(resp, pn, 400); sgp_w("\n" as *u8)
1121 sys_exit(9); return 9
1122 }
1123 // The ref ADVANCED, so the remote now provably holds every object of this snapshot -- persist the
1124 // have-set so the next beat sends only what changed. Written ONLY after a confirmed ok, never before.
1125 sys_mkdir("knowledge/sovgit_have" as *u8, 0x1ed)
1126 let hfd: i64 = sys_openat_wr(hpath, 0x1a4)
1127 if hfd >= 0 {
1128 sys_write(hfd, craw, 32)
1129 sys_write(hfd, allshas, pst[3] * 32)
1130 sys_close(hfd)
1131 } else { sgp_we("WARN could not persist have-set (next push falls back to a full pack)\n" as *u8) }
1132 sgp_w("SOVGIT-PUSH OK verdict=GREEN ref=" as *u8); sgp_w(refpath)
1133 sgp_w(" " as *u8); sgp_w(oldsha)
1134 sgp_w(" -> " as *u8); sgp_w(newsha)
1135 sgp_w(" objects=" as *u8); sgp_wn(pst[1])
1136 sgp_w(" pack=" as *u8); sgp_wn(packlen)
1137 sgp_w(" ZERO-GIT-BINARY\n" as *u8)
1138 return 0
1139}