code wiki / (root) / nx_ingest_cli.nx

nx_ingest_cli.nx source

↩ module page · 298 lines · 11359 B

1// nx_ingest_cli.nx -- CLI driver for the paste-HTML -> POV-caption pipeline. 2// 3// Reads raw HTML from stdin (up to 256 KiB), runs the full Tier 0-5 4// ingestion stack, and writes the POV-shifted Literotica caption to 5// stdout. Exit code 0 on success. 6// 7// Usage (under qemu-riscv64-static): 8// 9// cat chapter.html | qemu-riscv64-static _offc/nx_ingest_cli.elf 10// 11// Or via the bench wrapper: 12// 13// bench/nx_ingest_cli.sh path/to/chapter.html 14// 15// Workflow today (TLS-blocked): 16// 1. Browser -> open the source page (literotica chapter, nhentai 17// gallery, fanfiction.net chapter, anywhere) 18// 2. Ctrl+U to view source, save as .html OR copy-paste into a 19// file 20// 3. Pipe that file into this CLI 21// 4. POV-shifted first-person caption arrives on stdout 22// 23// Workflow once TLS unblocks (SHA-256 fix): 24// 1. The same CLI accepts a URL arg 25// 2. Internally calls nx_media_fetch URL transport 26// 3. Same pipeline 27// 28// Per cardinal feedback-user-owns-every-bit: this is a one-shot 29// process, no background daemon, no auto-cataloging without 30// caller opt-in. Output goes to stdout; the user redirects. 31// 32// nx_safety_envelope: 33// intended_use: "CLI adapter that runs paste-HTML through 34// the full ingestion + caption pipeline, 35// emitting POV-shifted output to stdout." 36// sil_target: SIL2 37// asil_target: QM 38// dal_target: DAL C 39// iec_62304_class: NONE 40// evidence: [no_floating_point, 41// bounded_read_loop, 42// single_pipeline_invocation_per_call, 43// deterministic_pov_gender_default] 44// hazard_register: [bug-tape-stdin-truncated-at-cap, 45// bug-tape-empty-input-returns-empty-output] 46// residual_risk: "Hard 256 KiB stdin cap; pasting larger 47// documents truncates silently. Future 48// revision streams in chunks." 49// verdict: NOT_YET_EVALUATED 50 51import "nx_syscalls.nx" 52import "nx_media_pool.nx" 53import "nx_storybeat.nx" 54import "nx_storydb.nx" 55import "nx_html_extract.nx" 56import "nx_text_ingest.nx" 57import "nx_term_registry.nx" 58import "nx_term_extract.nx" 59import "nx_term_cooccur.nx" 60import "nx_arc_anchor.nx" 61import "nx_scene_index.nx" 62import "nx_pov_shift.nx" 63import "nx_caption_emit.nx" 64import "nx_content_guard.nx" 65const NX_MAGIC_4096: i64 = 4096 66 67const NX_INGEST_INPUT_CAP: i64 = 262144 // 256 KiB 68const NX_INGEST_OUTPUT_CAP: i64 = 524288 // 512 KiB 69 70// Default protagonist gender for V1. V2: parse from CLI arg. 71const NX_INGEST_DEFAULT_GENDER: i64 = 1 // NX_POV_GENDER_HE 72 73// Default output format. V2: parse from CLI arg. 74const NX_INGEST_DEFAULT_FORMAT: i64 = 2 // NX_CAP_FORMAT_LITEROTICA 75 76func nx_ingest_read_stdin(buf: *u8, cap: i64) -> i64 { 77 var total: i64 = 0 78 let BUDGET: i64 = (cap / NX_MAGIC_4096) + 16 79 var iter: i64 = 0 80 var done: i64 = 0 81 while done == 0 { 82 if iter >= BUDGET { done = 1 } 83 if done == 0 { 84 if total >= cap { done = 1 } 85 if done == 0 { 86 let dst: *u8 = ((buf as i64) + total) as *u8 87 let want: i64 = cap - total 88 let got: i64 = sys_read(0, dst, want) 89 if got < 0 { done = 1 } 90 if got == 0 { done = 1 } 91 if got > 0 { total = total + got } 92 } 93 } 94 iter = iter + 1 95 } 96 return total 97} 98 99func nx_ingest_write_stdout(buf: *u8, n: i64) -> i64 { 100 if n <= 0 { return 0 } 101 var off: i64 = 0 102 let BUDGET: i64 = (n / NX_MAGIC_4096) + 16 103 var iter: i64 = 0 104 while off < n { 105 if iter >= BUDGET { off = n } 106 if off < n { 107 let src: *u8 = ((buf as i64) + off) as *u8 108 let want: i64 = n - off 109 let wrote: i64 = sys_write(1, src, want) 110 if wrote <= 0 { off = n } 111 if wrote > 0 { off = off + wrote } 112 } 113 iter = iter + 1 114 } 115 return off 116} 117 118// Write a literal byte string to stderr (informational). 119func nx_ingest_log(s: *u8, n: i64) -> i64 { 120 return sys_write(2, s, n) 121} 122 123func main() -> i64 { 124 // ---- 1. Read HTML from stdin ---- 125 let in_buf: *u8 = sys_mmap(NX_INGEST_INPUT_CAP) 126 let in_n: i64 = nx_ingest_read_stdin(in_buf, NX_INGEST_INPUT_CAP) 127 if in_n <= 0 { 128 nx_ingest_log("nx_ingest_cli: empty input\n" as *u8, 25) 129 return 1 130 } 131 132 // ---- 2. Extract title + body text from HTML ---- 133 let extract: *HtmlExtract = nx_html_extract(in_buf, in_n) 134 if extract.body_len <= 0 { 135 nx_ingest_log("nx_ingest_cli: html_extract produced no body\n" as *u8, 45) 136 return 2 137 } 138 139 // ---- 2b. Top-level content guard: refuse NTR-coded sources ---- 140 // 141 // Runs BEFORE the pipeline so even single-paragraph NTR sources 142 // (too short to produce arc anchors) still get refused. Per 143 // user spec: "i am the dominant actor acting on the scene not 144 // watching" -- multi-male-active scenes are NTR-coded unless 145 // the user IS the active POV. 146 let top_flags: i64 = nx_content_guard_scan_bytes(extract.body_buf, 147 extract.body_len) 148 let top_v: i64 = nx_content_guard_verdict(top_flags) 149 if top_v == NX_CG_VERDICT_CONFIRMED { 150 let exp_top: *u8 = sys_mmap(256) 151 let lt: i64 = nx_content_guard_emit_explain(top_flags, exp_top, 256) 152 if lt > 0 { sys_write(1, exp_top, lt) } 153 let tail: *u8 = "# nx_ingest_cli: source refused at top-level guard\n" as *u8 154 sys_write(1, tail, 52) 155 return 0 156 } 157 if top_v == NX_CG_VERDICT_SUSPECT { 158 let exp_top2: *u8 = sys_mmap(256) 159 let lt2: i64 = nx_content_guard_emit_explain(top_flags, exp_top2, 256) 160 if lt2 > 0 { sys_write(1, exp_top2, lt2) } 161 } 162 163 // ---- 3. Ingest body into StoryDb ---- 164 let db: *StoryDb = nx_storydb_alloc() 165 let mid: i64 = nx_media_pool_add(db.media_pool, NX_MEDIA_TYPE_TEXT, 166 NX_MEDIA_SRC_USER_PASTE, 167 extract.body_buf, extract.body_len, 168 0 as *u8, 0, 169 0 as *u8, 0, 170 0) 171 if mid < 0 { return 3 } 172 let irc: i64 = nx_text_ingest(db, mid) 173 if irc != NX_TEXT_INGEST_OK { 174 nx_ingest_log("nx_ingest_cli: text_ingest failed\n" as *u8, 35) 175 return 4 176 } 177 178 // ---- 4. Learn vocabulary + co-occurrence ---- 179 let reg: *TermRegistry = nx_term_registry_alloc() 180 if nx_term_extract_all(db, reg) != NX_EXTR_OK { 181 nx_ingest_log("nx_ingest_cli: term_extract failed\n" as *u8, 36) 182 return 5 183 } 184 let co: *Cooccur = nx_cooccur_alloc() 185 let _np: i64 = nx_cooccur_observe_db(co, db) 186 187 // ---- 5. Discover scene anchors ---- 188 let scratch_buf: *u8 = sys_mmap(32 * NX_COOCCUR_PAIR_BYTES) 189 let scratch: *CooccurPair = scratch_buf as *CooccurPair 190 let anchor_buf: *u8 = sys_mmap(16 * NX_ARC_ANCHOR_BYTES) 191 let anchors: *ArcAnchor = anchor_buf as *ArcAnchor 192 let n_anch: i64 = nx_arc_anchor_discover(co, reg, scratch, 32, 193 anchors, 16) 194 195 // ---- 6. Build scene index ---- 196 let idx: *SceneIndex = nx_scene_index_alloc() 197 let _nb: i64 = nx_scene_index_build(idx, db, anchors, n_anch) 198 199 // ---- 6b. Content guard: scan for NTR / multi-male-active / etc. ---- 200 // 201 // Per user spec: "i am the dominant actor acting on the scene 202 // not watching". NTR_CONFIRMED candidates get refused inline 203 // and excluded from the POV-shift output. SUSPECT candidates 204 // get a warning header but proceed. 205 let _ng: i64 = nx_content_guard_scan_index(idx, db) 206 var any_confirmed: i64 = 0 207 var any_suspect: i64 = 0 208 var ci_iter: i64 = 0 209 while ci_iter < idx.n_candidates { 210 let c: *SceneCandidate = nx_scene_candidate_at(idx, ci_iter) 211 let v: i64 = nx_content_guard_verdict(c.flags) 212 if v == NX_CG_VERDICT_CONFIRMED { any_confirmed = 1 } 213 if v == NX_CG_VERDICT_SUSPECT { any_suspect = 1 } 214 ci_iter = ci_iter + 1 215 } 216 217 if any_confirmed != 0 { 218 let exp_buf: *u8 = sys_mmap(512) 219 // Emit ALL refusal lines (one per CONFIRMED candidate). 220 var ej: i64 = 0 221 while ej < idx.n_candidates { 222 let c2: *SceneCandidate = nx_scene_candidate_at(idx, ej) 223 let v2: i64 = nx_content_guard_verdict(c2.flags) 224 if v2 == NX_CG_VERDICT_CONFIRMED { 225 let ln: i64 = nx_content_guard_emit_explain(c2.flags, 226 exp_buf, 512) 227 if ln > 0 { sys_write(1, exp_buf, ln) } 228 } 229 ej = ej + 1 230 } 231 let tail: *u8 = "# nx_ingest_cli: at least one CONFIRMED candidate; POV body suppressed\n" as *u8 232 sys_write(1, tail, 72) 233 return 0 234 } 235 236 if any_suspect != 0 { 237 let exp_buf2: *u8 = sys_mmap(512) 238 var ek: i64 = 0 239 while ek < idx.n_candidates { 240 let c3: *SceneCandidate = nx_scene_candidate_at(idx, ek) 241 let v3: i64 = nx_content_guard_verdict(c3.flags) 242 if v3 == NX_CG_VERDICT_SUSPECT { 243 let ln2: i64 = nx_content_guard_emit_explain(c3.flags, 244 exp_buf2, 512) 245 if ln2 > 0 { sys_write(1, exp_buf2, ln2) } 246 } 247 ek = ek + 1 248 } 249 } 250 251 // ---- 7. POV-shift the FULL body text ---- 252 // 253 // V1: shift the whole body in one pass. The companion 254 // surfacer later picks per-scene candidates and emits them in 255 // the chosen format. Stdout is the shifted full text so 256 // the user sees the literotica-style rewrite immediately. 257 let out_buf: *u8 = sys_mmap(NX_INGEST_OUTPUT_CAP) 258 let out_n: i64 = nx_pov_shift(NX_INGEST_DEFAULT_GENDER, 259 extract.body_buf, extract.body_len, 260 out_buf, NX_INGEST_OUTPUT_CAP) 261 if out_n <= 0 { 262 nx_ingest_log("nx_ingest_cli: pov_shift produced empty output\n" as *u8, 48) 263 return 6 264 } 265 266 // ---- 8. Emit ---- 267 // 268 // Header line (#) so downstream tools can split logical sections 269 // when we extend output with anchor metadata or RAW_JSON. 270 let hdr: *u8 = "# nx_ingest_cli: POV-shifted body (gender=he->I)\n" as *u8 271 nx_ingest_write_stdout(hdr, 50) 272 nx_ingest_write_stdout(out_buf, out_n) 273 nx_ingest_write_stdout("\n" as *u8, 1) 274 275 // Anchor summary to stderr so it doesn't pollute the captured 276 // POV-shifted text but the user still sees what got discovered. 277 nx_ingest_log("# anchors discovered: " as *u8, 22) 278 // Tiny int-to-decimal for n_anch (max 16 anchors). 279 let nb_buf: *u8 = sys_mmap(8) 280 if n_anch == 0 { nb_buf[0] = 0x30; sys_write(2, nb_buf, 1) } 281 if n_anch > 0 { 282 var x: i64 = n_anch 283 var dn: i64 = 0 284 while x > 0 { 285 nb_buf[dn] = 0x30 + (x - ((x / 10) * 10)) 286 dn = dn + 1 287 x = x / 10 288 } 289 var di: i64 = dn - 1 290 while di >= 0 { 291 sys_write(2, ((nb_buf as i64) + di) as *u8, 1) 292 di = di - 1 293 } 294 } 295 nx_ingest_log("\n" as *u8, 1) 296 297 return 0 298}