nx_pushback_lib.nx source
↩ module page · 408 lines · 19890 B
1// nx_pushback_lib.nx -- COMMUNITY PUSH-BACK WITH LICENCE, PROVENANCE AND THE REFEREE GRADE ATTACHED (/compare/modding MD23
2// pushback_publish, 2026-09-06). Our own models go back to the communities as ONE bundle per community target: the sovereign
3// mod manifest (nx_mod_manifest_lib, MD18) copied byte-for-byte, the package archived by the estate's own 7z writer
4// (nx_sevenz_lib sz_write_copy, MD29), the content id, magnet, download url and index row from the ONE transport addresser
5// (nx_dist_publish), and a publish row carrying the licence row, the member count, the manifest and archive digests and the
6// referee grade. Every door this lib composes is fail-closed and it adds no second ruler: the manifest must VERIFY against
7// the files as they are (mm_verify), the package licence is re-read from the ONE rights table (a hand-edited id is refused
8// LICENCE-UNKNOWN), every journaled member is re-asked from the ONE asset journal at publish time (a rip whose licence class
9// is unknown is refused PROVENANCE by name -- the rung's own words), the community's redistribution floor is applied to the
10// package and to every journaled member, and an UNGRADED package never publishes (the operator's law: presence is not
11// completion; a push-back without a referee grade is a poorly-done cut shipped to strangers). The community targets are DATA
12// (knowledge/modding_communities.conf); the same manifest bytes land in every bundle, which is the invariant the gate proves.
13// license_tier: ORIGINAL No hw writes (Rule 26).
14import "nx_syscalls.nx"
15import "nx_mod_manifest_lib.nx"
16import "nx_sevenz_lib.nx"
17import "nx_dist_publish.nx"
18
19const PB_CONF_DEFAULT: *u8 = "knowledge/modding_communities.conf"
20const PB_GRADE_DEFAULT_NAME: *u8 = "referee.grade"
21const PB_OUTDIR_DEFAULT_NAME: *u8 = "pushback"
22const PB_TAG: *u8 = "community"
23const PB_GRADE_TAG: *u8 = "grade"
24const PB_KIND_DIST_S: *u8 = "dist"
25const PB_KIND_APIDESC_S: *u8 = "api-desc"
26const PB_MANIFEST_EXT: *u8 = ".nxmod"
27const PB_ARCHIVE_EXT: *u8 = ".7z"
28const PB_INDEX_FILE: *u8 = "index.row"
29const PB_ROW_FILE: *u8 = "publish.row"
30const PB_TRANSPORT_DIST: *u8 = "dist-index"
31const PB_TRANSPORT_DESC: *u8 = "descriptor-awaiting-keyed-door"
32const PB_DASH_S: *u8 = "-"
33const PB_SEP: i64 = 124
34const PB_NL: i64 = 10
35const PB_HASH: i64 = 35
36const PB_DASH: i64 = 45
37const PB_SLASH: i64 = 47
38const PB_DIR_MODE: i64 = 493
39const PB_FILE_MODE: i64 = 420
40const PB_PATH_CAP: i64 = 1024
41const PB_ROW_CAP: i64 = 8192
42const PB_ARCHIVE_SLACK: i64 = 4096
43const PB_PER_MEMBER_SLACK: i64 = 512
44const PB_PERMIL_MAX: i64 = 1000
45const PB_MAX_COMMUNITIES: i64 = 64
46const PB_MAX_IDS: i64 = 64
47// community table slots (one row = PB_C_SLOTS i64s)
48const PB_C_ID: i64 = 0
49const PB_C_KIND: i64 = 1
50const PB_C_ENDPOINT: i64 = 2
51const PB_C_MINREDIST: i64 = 3
52const PB_C_NOTE: i64 = 4
53const PB_C_SLOTS: i64 = 6
54const PB_KIND_UNKNOWN: i64 = 0
55const PB_KIND_DIST: i64 = 1
56const PB_KIND_APIDESC: i64 = 2
57// results, every one NAMED by pb_err_name
58const PB_OK: i64 = 0
59const PB_ERR_CONF_UNREADABLE: i64 = 0 - 1
60const PB_ERR_NO_SUCH_COMMUNITY: i64 = 0 - 2
61const PB_ERR_MANIFEST_UNREADABLE: i64 = 0 - 3
62const PB_ERR_MANIFEST_REFUSED: i64 = 0 - 4
63const PB_ERR_LICENCE_UNKNOWN: i64 = 0 - 5
64const PB_ERR_NON_REDISTRIBUTABLE: i64 = 0 - 6
65const PB_ERR_PROVENANCE: i64 = 0 - 7
66const PB_ERR_UNGRADED: i64 = 0 - 8
67const PB_ERR_OUT_UNWRITABLE: i64 = 0 - 9
68const PB_ERR_ARCHIVE: i64 = 0 - 10
69const PB_ERR_MEMBER_UNREADABLE: i64 = 0 - 11
70const PB_ERR_TABLE_UNREADABLE: i64 = 0 - 12
71const PB_ERR_KIND_UNKNOWN: i64 = 0 - 13
72// per-community result slots
73const PB_R_RC: i64 = 0
74const PB_R_KIND: i64 = 1
75const PB_R_ARCHIVE_BYTES: i64 = 2
76const PB_R_CID: i64 = 3 // *u8 65
77const PB_R_MANIFEST_SHA: i64 = 4 // *u8 65, hashed from the WRITTEN copy
78const PB_R_OUTDIR: i64 = 5 // *u8 the bundle directory
79const PB_R_MEMBER_IDX: i64 = 6 // the member that refused, or -1
80const PB_R_REASON: i64 = 7 // the pv reason, the mm verdict, or the redist level that fell below the floor
81const PB_R_N: i64 = 8
82// referee grade slots
83const PB_G_PERMIL: i64 = 0
84const PB_G_JUDGE: i64 = 1
85const PB_G_DATE: i64 = 2
86const PB_G_N: i64 = 4
87// request summary slots
88const PB_S_REQUESTED: i64 = 0
89const PB_S_PUBLISHED: i64 = 1
90const PB_S_REFUSED: i64 = 2
91const PB_S_IDENTICAL: i64 = 3
92const PB_S_RC: i64 = 4
93const PB_S_CONF_ROWS: i64 = 5
94const PB_S_MEMBERS: i64 = 6
95const PB_S_PERMIL: i64 = 7
96const PB_S_N: i64 = 8
97
98func pb_write_file(path: *u8, b: *u8, n: i64) -> i64 {
99 let fd: i64 = sys_openat_wr(path, PB_FILE_MODE)
100 if fd < 0 { return 0 - 1 }
101 var done: i64 = 0
102 while done < n { let k: i64 = sys_write(fd, b + done, n - done); if k <= 0 { break } done = done + k }
103 sys_close(fd)
104 return done
105}
106func pb_sep(row: *u8, o: i64) -> i64 { row[o] = PB_SEP as u8; return o + 1 }
107func pb_kind_code(s: *u8) -> i64 {
108 if mm_streq(s, PB_KIND_DIST_S) == 1 { return PB_KIND_DIST }
109 if mm_streq(s, PB_KIND_APIDESC_S) == 1 { return PB_KIND_APIDESC }
110 return PB_KIND_UNKNOWN
111}
112func pb_kind_name(k: i64) -> *u8 {
113 if k == PB_KIND_DIST { return PB_KIND_DIST_S }
114 if k == PB_KIND_APIDESC { return PB_KIND_APIDESC_S }
115 return "unknown" as *u8
116}
117func pb_transport_name(k: i64) -> *u8 {
118 if k == PB_KIND_DIST { return PB_TRANSPORT_DIST }
119 return PB_TRANSPORT_DESC
120}
121func pb_err_name(rc: i64) -> *u8 {
122 if rc == PB_OK { return "OK" as *u8 }
123 if rc == PB_ERR_CONF_UNREADABLE { return "CONF-UNREADABLE" as *u8 }
124 if rc == PB_ERR_NO_SUCH_COMMUNITY { return "NO-SUCH-COMMUNITY" as *u8 }
125 if rc == PB_ERR_MANIFEST_UNREADABLE { return "MANIFEST-UNREADABLE" as *u8 }
126 if rc == PB_ERR_MANIFEST_REFUSED { return "MANIFEST-REFUSED" as *u8 }
127 if rc == PB_ERR_LICENCE_UNKNOWN { return "LICENCE-UNKNOWN" as *u8 }
128 if rc == PB_ERR_NON_REDISTRIBUTABLE { return "NON-REDISTRIBUTABLE" as *u8 }
129 if rc == PB_ERR_PROVENANCE { return "PROVENANCE" as *u8 }
130 if rc == PB_ERR_UNGRADED { return "UNGRADED" as *u8 }
131 if rc == PB_ERR_OUT_UNWRITABLE { return "OUT-UNWRITABLE" as *u8 }
132 if rc == PB_ERR_ARCHIVE { return "ARCHIVE" as *u8 }
133 if rc == PB_ERR_MEMBER_UNREADABLE { return "MEMBER-UNREADABLE" as *u8 }
134 if rc == PB_ERR_TABLE_UNREADABLE { return "RIGHTS-TABLE-UNREADABLE" as *u8 }
135 if rc == PB_ERR_KIND_UNKNOWN { return "KIND-UNKNOWN" as *u8 }
136 return "UNKNOWN-RC" as *u8
137}
138// ---- the community table: community|id|kind|endpoint|minredist|note ----
139func pb_conf_load(path: *u8, tbl: *i64, cap: i64) -> i64 {
140 let lp: *i64 = sys_mmap(16) as *i64
141 let b: *u8 = sys_read_file(path, lp)
142 if (b as i64) == 0 { return PB_ERR_CONF_UNREADABLE }
143 let n: i64 = lp[0]
144 let offs: *i64 = sys_mmap(8 * MM_MAX_FIELDS) as *i64
145 let lens: *i64 = sys_mmap(8 * MM_MAX_FIELDS) as *i64
146 var rows: i64 = 0
147 var p: i64 = 0
148 while p < n {
149 var e: i64 = p
150 while e < n { if (b[e] as i64) == PB_NL { break } e = e + 1 }
151 if e > p { if (b[p] as i64) != PB_HASH {
152 let nf: i64 = mm_fields(b, p, e, offs, lens)
153 if nf >= 5 { if mm_is(b, offs[0], lens[0], PB_TAG) == 1 { if rows < cap {
154 let base: i64 = rows * PB_C_SLOTS
155 tbl[base + PB_C_ID] = (mm_dup(b + offs[1], lens[1])) as i64
156 tbl[base + PB_C_KIND] = (mm_dup(b + offs[2], lens[2])) as i64
157 tbl[base + PB_C_ENDPOINT] = (mm_dup(b + offs[3], lens[3])) as i64
158 tbl[base + PB_C_MINREDIST] = mm_atoi(b + offs[4], lens[4])
159 var note: i64 = PB_DASH_S as i64
160 if nf >= 6 { note = (mm_dup(b + offs[5], lens[5])) as i64 }
161 tbl[base + PB_C_NOTE] = note
162 rows = rows + 1
163 } } }
164 } }
165 p = e + 1
166 }
167 return rows
168}
169func pb_find(tbl: *i64, n: i64, id: *u8) -> i64 {
170 var i: i64 = 0
171 while i < n { if mm_streq(tbl[i * PB_C_SLOTS + PB_C_ID] as *u8, id) == 1 { return i } i = i + 1 }
172 return 0 - 1
173}
174// ---- the referee grade: a file whose first grade row reads grade|<permil>|<judge>|<date> ----
175func pb_grade_read(path: *u8, g: *i64) -> i64 {
176 g[PB_G_PERMIL] = 0 - 1
177 g[PB_G_JUDGE] = PB_DASH_S as i64
178 g[PB_G_DATE] = PB_DASH_S as i64
179 if (path as i64) == 0 { return PB_ERR_UNGRADED }
180 if path[0] == (0 as u8) { return PB_ERR_UNGRADED }
181 let lp: *i64 = sys_mmap(16) as *i64
182 let b: *u8 = sys_read_file(path, lp)
183 if (b as i64) == 0 { return PB_ERR_UNGRADED }
184 let n: i64 = lp[0]
185 let offs: *i64 = sys_mmap(8 * MM_MAX_FIELDS) as *i64
186 let lens: *i64 = sys_mmap(8 * MM_MAX_FIELDS) as *i64
187 var p: i64 = 0
188 while p < n {
189 var e: i64 = p
190 while e < n { if (b[e] as i64) == PB_NL { break } e = e + 1 }
191 if e > p { if (b[p] as i64) != PB_HASH {
192 let nf: i64 = mm_fields(b, p, e, offs, lens)
193 if nf >= 4 { if mm_is(b, offs[0], lens[0], PB_GRADE_TAG) == 1 {
194 let permil: i64 = mm_atoi(b + offs[1], lens[1])
195 if permil < 0 { return PB_ERR_UNGRADED }
196 if permil > PB_PERMIL_MAX { return PB_ERR_UNGRADED }
197 g[PB_G_PERMIL] = permil
198 g[PB_G_JUDGE] = (mm_dup(b + offs[2], lens[2])) as i64
199 g[PB_G_DATE] = (mm_dup(b + offs[3], lens[3])) as i64
200 return PB_OK
201 } }
202 } }
203 p = e + 1
204 }
205 return PB_ERR_UNGRADED
206}
207// ---- ONE community: verify, licence floor, provenance floor, then the bundle ----
208func pb_publish_one(root: *u8, mb: *u8, mn: i64, m: *i64, crow: *i64, outdir: *u8, g: *i64, jrnl: *u8, r: *i64) -> i64 {
209 var q: i64 = 0
210 while q < PB_R_N { r[q] = 0; q = q + 1 }
211 r[PB_R_MEMBER_IDX] = 0 - 1
212 r[PB_R_KIND] = pb_kind_code(crow[PB_C_KIND] as *u8)
213 r[PB_R_CID] = (sys_mmap(MM_SHA_HEX + 1)) as i64
214 r[PB_R_MANIFEST_SHA] = (sys_mmap(MM_SHA_HEX + 1)) as i64
215 r[PB_R_OUTDIR] = (sys_mmap(PB_PATH_CAP)) as i64
216 if r[PB_R_KIND] == PB_KIND_UNKNOWN { r[PB_R_RC] = PB_ERR_KIND_UNKNOWN; return PB_ERR_KIND_UNKNOWN }
217 if g[PB_G_PERMIL] < 0 { r[PB_R_RC] = PB_ERR_UNGRADED; return PB_ERR_UNGRADED }
218 // 1. the manifest must verify against the files as they ARE (tampered, missing and forged refuse here)
219 let rv: *i64 = sys_mmap(8 * MM_R_N) as *i64
220 let v: i64 = mm_verify(m, root, rv)
221 if v != MM_V_ACCEPT { r[PB_R_REASON] = v; r[PB_R_RC] = PB_ERR_MANIFEST_REFUSED; return PB_ERR_MANIFEST_REFUSED }
222 // 2. the package licence, re-read from the ONE rights table, against the community's floor
223 let lic: *u8 = m[MM_M_LIC] as *u8
224 let ctx: *i64 = lg_ctx()
225 if ctx[3] < 0 { r[PB_R_RC] = PB_ERR_TABLE_UNREADABLE; return PB_ERR_TABLE_UNREADABLE }
226 let li: i64 = lg_find_license(ctx, lic)
227 if li < 0 { r[PB_R_RC] = PB_ERR_LICENCE_UNKNOWN; return PB_ERR_LICENCE_UNKNOWN }
228 var redist: i64 = lg_lic_redist(ctx, li)
229 if lg_lic_verified(ctx, li) == LG_UNVERIFIED { if redist > LG_COND { redist = LG_COND } }
230 let minr: i64 = crow[PB_C_MINREDIST]
231 r[PB_R_REASON] = redist
232 if redist < minr { r[PB_R_RC] = PB_ERR_NON_REDISTRIBUTABLE; return PB_ERR_NON_REDISTRIBUTABLE }
233 // 3. every member's provenance, LIVE from the journal at publish time (the manifest's prov rows are a snapshot)
234 let nc: i64 = m[MM_M_NCONTENT]
235 let shas: *i64 = m[MM_M_SHAS] as *i64
236 let paths: *i64 = m[MM_M_PATHS] as *i64
237 let res: *i64 = sys_mmap(8 * PV_RES_N) as *i64
238 var i: i64 = 0
239 while i < nc {
240 pv_verdict(jrnl, shas[i] as *u8, res)
241 let reason: i64 = res[PV_RES_REASON]
242 var bad: i64 = 0
243 if reason == PV_R_UNKNOWN_LIC { bad = 1 }
244 if reason == PV_R_NON_REDIST { bad = 1 }
245 if reason == PV_R_TABLE_UNREADABLE { bad = 1 }
246 if bad == 1 { r[PB_R_MEMBER_IDX] = i; r[PB_R_REASON] = reason; r[PB_R_RC] = PB_ERR_PROVENANCE; return PB_ERR_PROVENANCE }
247 var journaled: i64 = 1
248 if reason == PV_R_NO_ROW { journaled = 0 }
249 if reason == PV_R_NO_JOURNAL { journaled = 0 }
250 if journaled == 1 { if res[PV_RES_REDIST] < minr {
251 r[PB_R_MEMBER_IDX] = i; r[PB_R_REASON] = res[PV_RES_REDIST]; r[PB_R_RC] = PB_ERR_NON_REDISTRIBUTABLE; return PB_ERR_NON_REDISTRIBUTABLE
252 } }
253 i = i + 1
254 }
255 // 4. the bundle directory and the byte-identical manifest copy
256 sys_mkdir(outdir, PB_DIR_MODE)
257 let cdir: *u8 = r[PB_R_OUTDIR] as *u8
258 mm_join_path(outdir, crow[PB_C_ID] as *u8, cdir)
259 sys_mkdir(cdir, PB_DIR_MODE)
260 let stem: *u8 = sys_mmap(PB_PATH_CAP)
261 var so: i64 = mm_cat(stem, 0, m[MM_M_NAME] as *u8)
262 stem[so] = PB_DASH as u8
263 so = so + 1
264 so = mm_cat(stem, so, m[MM_M_VERSION] as *u8)
265 stem[so] = 0 as u8
266 let mname: *u8 = sys_mmap(PB_PATH_CAP)
267 var mo: i64 = mm_cat(mname, 0, stem)
268 mo = mm_cat(mname, mo, PB_MANIFEST_EXT)
269 mname[mo] = 0 as u8
270 let aname: *u8 = sys_mmap(PB_PATH_CAP)
271 var ao: i64 = mm_cat(aname, 0, stem)
272 ao = mm_cat(aname, ao, PB_ARCHIVE_EXT)
273 aname[ao] = 0 as u8
274 let mcopy: *u8 = sys_mmap(PB_PATH_CAP)
275 mm_join_path(cdir, mname, mcopy)
276 if pb_write_file(mcopy, mb, mn) != mn { r[PB_R_RC] = PB_ERR_OUT_UNWRITABLE; return PB_ERR_OUT_UNWRITABLE }
277 if pv_hash_file(mcopy, r[PB_R_MANIFEST_SHA] as *u8) != mn { r[PB_R_RC] = PB_ERR_OUT_UNWRITABLE; return PB_ERR_OUT_UNWRITABLE }
278 // 5. the archive: the manifest first, then every member, through the estate's own 7z writer
279 let nf: i64 = nc + 1
280 let names: *i64 = sys_mmap(8 * (nf + 1)) as *i64
281 let datas: *i64 = sys_mmap(8 * (nf + 1)) as *i64
282 let lens: *i64 = sys_mmap(8 * (nf + 1)) as *i64
283 names[0] = mname as i64
284 datas[0] = mb as i64
285 lens[0] = mn
286 var total: i64 = mn
287 i = 0
288 while i < nc {
289 let full: *u8 = sys_mmap(PB_PATH_CAP * 2)
290 mm_join_path(root, paths[i] as *u8, full)
291 let lp: *i64 = sys_mmap(16) as *i64
292 let d: *u8 = sys_read_file(full, lp)
293 if (d as i64) == 0 { r[PB_R_MEMBER_IDX] = i; r[PB_R_RC] = PB_ERR_MEMBER_UNREADABLE; return PB_ERR_MEMBER_UNREADABLE }
294 names[i + 1] = paths[i]
295 datas[i + 1] = d as i64
296 lens[i + 1] = lp[0]
297 total = total + lp[0]
298 i = i + 1
299 }
300 let acap: i64 = total + PB_ARCHIVE_SLACK + nf * (PB_PER_MEMBER_SLACK + PB_PATH_CAP)
301 let arch: *u8 = sys_mmap(acap)
302 let an: i64 = sz_write_copy(names, datas, lens, nf, arch, acap)
303 if an <= 0 { r[PB_R_REASON] = an; r[PB_R_RC] = PB_ERR_ARCHIVE; return PB_ERR_ARCHIVE }
304 let apath: *u8 = sys_mmap(PB_PATH_CAP)
305 mm_join_path(cdir, aname, apath)
306 if pb_write_file(apath, arch, an) != an { r[PB_R_RC] = PB_ERR_OUT_UNWRITABLE; return PB_ERR_OUT_UNWRITABLE }
307 r[PB_R_ARCHIVE_BYTES] = an
308 // 6. content id, magnet, download url and the index row: the ONE transport addresser (nx_dist_publish)
309 let cid: *u8 = r[PB_R_CID] as *u8
310 let cb: i64 = dp_content_id(apath, cid)
311 if cb != an { r[PB_R_REASON] = cb; r[PB_R_RC] = PB_ERR_OUT_UNWRITABLE; return PB_ERR_OUT_UNWRITABLE }
312 let magnet: *u8 = sys_mmap(PB_ROW_CAP)
313 dp_magnet(cid, aname, magnet)
314 let url: *u8 = sys_mmap(PB_ROW_CAP)
315 dp_dl_url(cid, aname, url)
316 let row: *u8 = sys_mmap(PB_ROW_CAP)
317 var ro: i64 = dp_index_row(aname, cid, an, magnet, url, row)
318 row[ro] = PB_NL as u8
319 ro = ro + 1
320 let ipath: *u8 = sys_mmap(PB_PATH_CAP)
321 mm_join_path(cdir, PB_INDEX_FILE, ipath)
322 if pb_write_file(ipath, row, ro) != ro { r[PB_R_RC] = PB_ERR_OUT_UNWRITABLE; return PB_ERR_OUT_UNWRITABLE }
323 // 7. the publish row: what went, under which licence, with which referee grade, by which transport
324 var po: i64 = mm_cat(row, 0, "pushback|" as *u8)
325 po = mm_cat(row, po, crow[PB_C_ID] as *u8); po = pb_sep(row, po)
326 po = mm_cat(row, po, crow[PB_C_KIND] as *u8); po = pb_sep(row, po)
327 po = mm_cat(row, po, crow[PB_C_ENDPOINT] as *u8); po = pb_sep(row, po)
328 po = mm_cat(row, po, m[MM_M_NAME] as *u8); po = pb_sep(row, po)
329 po = mm_cat(row, po, m[MM_M_VERSION] as *u8); po = pb_sep(row, po)
330 po = mm_cat(row, po, lic); po = pb_sep(row, po)
331 po = mm_putd(row, po, redist); po = pb_sep(row, po)
332 po = mm_putd(row, po, nc); po = pb_sep(row, po)
333 po = mm_cat(row, po, r[PB_R_MANIFEST_SHA] as *u8); po = pb_sep(row, po)
334 po = mm_cat(row, po, cid); po = pb_sep(row, po)
335 po = mm_putd(row, po, an); po = pb_sep(row, po)
336 po = mm_putd(row, po, g[PB_G_PERMIL]); po = pb_sep(row, po)
337 po = mm_cat(row, po, g[PB_G_JUDGE] as *u8); po = pb_sep(row, po)
338 po = mm_cat(row, po, g[PB_G_DATE] as *u8); po = pb_sep(row, po)
339 po = mm_cat(row, po, pb_transport_name(r[PB_R_KIND]))
340 row[po] = PB_NL as u8
341 po = po + 1
342 let rpath: *u8 = sys_mmap(PB_PATH_CAP)
343 mm_join_path(cdir, PB_ROW_FILE, rpath)
344 if pb_write_file(rpath, row, po) != po { r[PB_R_RC] = PB_ERR_OUT_UNWRITABLE; return PB_ERR_OUT_UNWRITABLE }
345 r[PB_R_RC] = PB_OK
346 return PB_OK
347}
348// ---- the request: N communities from one manifest; rows carries PB_R_N slots per requested community ----
349// returns the published count, or a NAMED negative rc when the request refuses before any community is tried
350func pb_publish(root: *u8, mpath: *u8, ids_csv: *u8, outdir: *u8, gradepath: *u8, confpath: *u8, jrnl: *u8, sum: *i64, rows: *i64) -> i64 {
351 var q: i64 = 0
352 while q < PB_S_N { sum[q] = 0; q = q + 1 }
353 sum[PB_S_PERMIL] = 0 - 1
354 var conf: *u8 = confpath
355 if (conf as i64) == 0 { conf = PB_CONF_DEFAULT }
356 if conf[0] == (0 as u8) { conf = PB_CONF_DEFAULT }
357 let tbl: *i64 = sys_mmap(8 * PB_C_SLOTS * PB_MAX_COMMUNITIES) as *i64
358 let nconf: i64 = pb_conf_load(conf, tbl, PB_MAX_COMMUNITIES)
359 if nconf < 0 { sum[PB_S_RC] = PB_ERR_CONF_UNREADABLE; return PB_ERR_CONF_UNREADABLE }
360 sum[PB_S_CONF_ROWS] = nconf
361 let lp: *i64 = sys_mmap(16) as *i64
362 let mb: *u8 = sys_read_file(mpath, lp)
363 if (mb as i64) == 0 { sum[PB_S_RC] = PB_ERR_MANIFEST_UNREADABLE; return PB_ERR_MANIFEST_UNREADABLE }
364 let m: *i64 = sys_mmap(8 * MM_M_N_SLOTS) as *i64
365 if mm_parse(mb, lp[0], m) != MM_OK { sum[PB_S_RC] = PB_ERR_MANIFEST_UNREADABLE; return PB_ERR_MANIFEST_UNREADABLE }
366 sum[PB_S_MEMBERS] = m[MM_M_NCONTENT]
367 let g: *i64 = sys_mmap(8 * PB_G_N) as *i64
368 pb_grade_read(gradepath, g)
369 sum[PB_S_PERMIL] = g[PB_G_PERMIL]
370 let parts: *i64 = sys_mmap(8 * PB_MAX_IDS) as *i64
371 let k: i64 = mm_split_csv(ids_csv, parts, PB_MAX_IDS)
372 sum[PB_S_REQUESTED] = k
373 var published: i64 = 0
374 var refused: i64 = 0
375 var identical: i64 = 1
376 var first: *u8 = 0 as *u8
377 var j: i64 = 0
378 while j < k {
379 let r: *i64 = ((rows as i64) + j * PB_R_N * 8) as *i64
380 var w: i64 = 0
381 while w < PB_R_N { r[w] = 0; w = w + 1 }
382 r[PB_R_MEMBER_IDX] = 0 - 1
383 let idx: i64 = pb_find(tbl, nconf, parts[j] as *u8)
384 if idx < 0 { r[PB_R_RC] = PB_ERR_NO_SUCH_COMMUNITY; refused = refused + 1 }
385 else {
386 let rc: i64 = pb_publish_one(root, mb, lp[0], m, ((tbl as i64) + idx * PB_C_SLOTS * 8) as *i64, outdir, g, jrnl, r)
387 if rc == PB_OK {
388 published = published + 1
389 if (first as i64) == 0 { first = r[PB_R_MANIFEST_SHA] as *u8 }
390 else { if mm_streq(first, r[PB_R_MANIFEST_SHA] as *u8) == 0 { identical = 0 } }
391 } else {
392 refused = refused + 1
393 if sum[PB_S_RC] == 0 { sum[PB_S_RC] = rc }
394 }
395 }
396 j = j + 1
397 }
398 sum[PB_S_PUBLISHED] = published
399 sum[PB_S_REFUSED] = refused
400 if published == 0 { identical = 0 }
401 sum[PB_S_IDENTICAL] = identical
402 return published
403}
404// THE CONTRACT SYMBOL the board watches (modding MD23): a thin name over pb_publish so the watch flips only when the real
405// entry exists and the command reaches it
406func pushback_publish(root: *u8, mpath: *u8, ids_csv: *u8, outdir: *u8, gradepath: *u8, confpath: *u8, jrnl: *u8, sum: *i64, rows: *i64) -> i64 {
407 return pb_publish(root, mpath, ids_csv, outdir, gradepath, confpath, jrnl, sum, rows)
408}