code wiki / (root) / nx_plane_migrate.nx

nx_plane_migrate.nx source

↩ module page · 207 lines · 9201 B

1// nx_plane_migrate.nx -- migrate a plane's VALUE ENCODING from flat tab-delimited lines to NXR1 records 2// (seq1326 rung 2, the actual retirement). ADDITIVE BY CONSTRUCTION: it writes to a NEW destination 3// prefix and NEVER touches the source, so rule 13 holds and a bad migration costs a wasted prefix, not data. 4// 5// ORDER OF OPERATIONS IS THE SAFETY PROPERTY: 6// 1. load the source with the HONEST loader 7// 2. REFUSE BEFORE WRITING ANYTHING if the read was truncated or the plane was empty -- migrating a 8// partial read would silently drop rows into a plane that then looks authoritative. This is the 9// lesson nx_plane_check taught me by catching my own false GREEN: a checker that has not seen every 10// row must never act as if it has. 11// 3. write every row as NXR1 under the same q:<seq> key 12// 4. VERIFY by reading the destination back and rendering each record to TSV, comparing byte-for-byte 13// against the source line. Verification reads the STORE, not the in-memory buffer, so it exercises 14// the real commit path rather than trusting the writer. 15// 16// WHY THE KEY STAYS q:<seq> FOR NOW: this rung changes the ENCODING only. Re-keying to business keys is a 17// separate rung with its own blast radius, and doing both at once would make a failure impossible to 18// attribute. One change, one gate. 19// 20// EXIT: 0 migrated+verified · 2 usage · 3 verify mismatch · 4 source empty · 6 source read TRUNCATED 21// 7 too many rows for the row table · 8 commit failed 22// license_tier: ORIGINAL No hw writes (Rule 26). 23import "nx_record_tsv.nx" 24import "nx_store_seed_lib.nx" 25 26const PM_CAP: i64 = 16777216 27const PM_RECCAP: i64 = 262144 28const PM_OUTCAP: i64 = 262144 29const PM_MSGCAP: i64 = 4096 30const PM_TYPECAP: i64 = 512 31const PM_FLAGS: i64 = 64 32const PM_MAXROWS: i64 = 65536 33const PM_KEYCAP: i64 = 64 34const PM_NUMCAP: i64 = 32 35const PM_SLACK: i64 = 1048576 36const PM_GROWTH: i64 = 3 // NXR1 adds 7B/field + 6B header; 3x the flat bytes is a safe arena 37const PM_NL: i64 = 10 38const PM_CH_I: i64 = 105 39const PM_STDOUT: i64 = 1 40const PM_KIND_LIVE: i64 = 1 41const PM_EXIT_USAGE: i64 = 2 42const PM_EXIT_MISMATCH: i64 = 3 43const PM_EXIT_EMPTY: i64 = 4 44const PM_EXIT_TRUNCATED: i64 = 6 45const PM_EXIT_TOOMANY: i64 = 7 46const PM_EXIT_COMMIT: i64 = 8 47const PM_EXIT_LOSSY: i64 = 9 48 49func pm_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 50func pm_cat(d: *u8, off: i64, s: *u8) -> i64 { 51 var i: i64 = 0 52 while s[i] != (0 as u8) { d[off + i] = s[i]; i = i + 1 } 53 return off + i 54} 55 56func main(argc: i64, argv: *i64) -> i64 { 57 let msg: *u8 = sys_mmap(PM_MSGCAP) 58 if argc < 3 { 59 var u: i64 = pm_cat(msg, 0, "usage: nx_plane_migrate <src-prefix-> <dst-prefix-> [typespec]\n writes NXR1 records to dst, never touches src; verifies by rendering back to TSV\n" as *u8) 60 sys_write(PM_STDOUT, msg, u) 61 return PM_EXIT_USAGE 62 } 63 let src: *u8 = argv[1] as *u8 64 let dst: *u8 = argv[2] as *u8 65 66 let types: *u8 = sys_mmap(PM_TYPECAP) 67 var ntypes: i64 = 0 68 if argc >= 4 { 69 let ts: *u8 = argv[3] as *u8 70 let tn: i64 = pm_len(ts) 71 var i: i64 = 0 72 while i < tn { 73 if i < PM_TYPECAP { 74 if ts[i] == (PM_CH_I as u8) { types[i] = NXR_T_I64 as u8 } else { types[i] = NXR_T_STR as u8 } 75 } 76 i = i + 1 77 } 78 ntypes = tn 79 if ntypes > PM_TYPECAP { ntypes = PM_TYPECAP } 80 } 81 82 let buf: *u8 = sys_mmap(PM_CAP) 83 let flags: *i64 = sys_mmap(PM_FLAGS) as *i64 84 let n: i64 = sts_load_honest(src, buf, PM_CAP, flags) 85 86 // index the source lines ONCE so the write pass and the verify pass see exactly the same rows. 87 let offs: *i64 = sys_mmap(8 * PM_MAXROWS) as *i64 88 let lens: *i64 = sys_mmap(8 * PM_MAXROWS) as *i64 89 var rows: i64 = 0 90 var over: i64 = 0 91 var start: i64 = 0 92 var i2: i64 = 0 93 while i2 <= n { 94 var atend: i64 = 0 95 if i2 == n { atend = 1 } else { if buf[i2] == (PM_NL as u8) { atend = 1 } } 96 if atend == 1 { 97 let llen: i64 = i2 - start 98 if llen > 0 { 99 if rows < PM_MAXROWS { offs[rows] = start; lens[rows] = llen; rows = rows + 1 } else { over = 1 } 100 } 101 start = i2 + 1 102 } 103 i2 = i2 + 1 104 } 105 106 // ---- REFUSE BEFORE WRITING. Every guard here runs while the destination is still untouched. 107 var o: i64 = pm_cat(msg, 0, "NX-PLANE-MIGRATE src=" as *u8) 108 o = pm_cat(msg, o, src) 109 o = pm_cat(msg, o, " dst=" as *u8) 110 o = pm_cat(msg, o, dst) 111 o = pm_cat(msg, o, " rows=" as *u8); o = rtv_itoa(msg, o, rows) 112 o = pm_cat(msg, o, " loaded_rows=" as *u8); o = rtv_itoa(msg, o, flags[1]) 113 o = pm_cat(msg, o, " bytes=" as *u8); o = rtv_itoa(msg, o, n) 114 o = pm_cat(msg, o, " cap=" as *u8); o = rtv_itoa(msg, o, PM_CAP) 115 116 if over == 1 { 117 o = pm_cat(msg, o, " verdict=RED-TOO-MANY-ROWS-nothing-written\n" as *u8) 118 sys_write(PM_STDOUT, msg, o); return PM_EXIT_TOOMANY 119 } 120 if rows == 0 { 121 o = pm_cat(msg, o, " verdict=RED-EMPTY-nothing-written\n" as *u8) 122 sys_write(PM_STDOUT, msg, o); return PM_EXIT_EMPTY 123 } 124 // ★★★LOSSY-SOURCE GUARD (added 2026-07-30 AFTER I violated it on the debt- plane). flags[0] is the 125 // plane's DECLARED q:n and flags[1] is what the loader could actually reach. When declared > loaded, 126 // rows are ALREADY unreachable -- and rewriting the plane with a NEW q:n equal to the loaded count 127 // BAKES THAT LOSS IN and DESTROYS THE SIGNAL that something is wrong. nx_debt already had exactly this 128 // guard ("DEBT-REFUSED lossy load ... committing would BAKE the loss into the next generation") and I 129 // shipped a migrator without it, then silently converted a flagged-lossy debt- plane into a 130 // self-consistent one. The rows were unreachable either way; what I removed was the ALARM. 131 // A MIGRATOR MUST NEVER MAKE A DAMAGED PLANE LOOK HEALTHY. 132 var lossy: i64 = 0 133 if flags[0] > flags[1] { lossy = 1 } 134 var trunc: i64 = 0 135 if n >= PM_CAP { trunc = 1 } 136 if rows != flags[1] { trunc = 1 } 137 if trunc == 1 { 138 o = pm_cat(msg, o, " verdict=RED-SOURCE-READ-TRUNCATED-nothing-written\n" as *u8) 139 sys_write(PM_STDOUT, msg, o); return PM_EXIT_TRUNCATED 140 } 141 if lossy == 1 { 142 o = pm_cat(msg, o, " declared_qn=" as *u8); o = rtv_itoa(msg, o, flags[0]) 143 o = pm_cat(msg, o, " verdict=RED-SOURCE-PLANE-IS-LOSSY-declared-exceeds-loadable-REPAIR-FIRST-nothing-written\n" as *u8) 144 sys_write(PM_STDOUT, msg, o); return PM_EXIT_LOSSY 145 } 146 147 // ---- WRITE PASS: one segment carrying every row as NXR1 plus the q:n count. 148 let w: *i64 = ss_begin_cap(n * PM_GROWTH + PM_SLACK) 149 let rec: *u8 = sys_mmap(PM_RECCAP) 150 let key: *u8 = sys_mmap(PM_KEYCAP) 151 var r: i64 = 0 152 while r < rows { 153 let lp: *u8 = (buf as i64 + offs[r]) as *u8 154 let rn: i64 = rtv_to_nxr(lp, lens[r], types, ntypes, rec) 155 sts_rowkey(r, key) 156 ss_add(w, PM_KIND_LIVE, key, rec, rn) 157 r = r + 1 158 } 159 let cntb: *u8 = sys_mmap(PM_NUMCAP) 160 let cn: i64 = rtv_itoa(cntb, 0, rows) 161 ss_add(w, PM_KIND_LIVE, "q:n" as *u8, cntb, cn) 162 if ss_commit(dst, w, ss_next_segid(dst)) != 0 { 163 o = pm_cat(msg, o, " verdict=RED-COMMIT-FAILED\n" as *u8) 164 sys_write(PM_STDOUT, msg, o); return PM_EXIT_COMMIT 165 } 166 167 // ---- VERIFY PASS: read the DESTINATION STORE back (not the in-memory buffer) and render each record 168 // to TSV, comparing byte-for-byte with the source line. This exercises the real commit path. 169 let h: *i64 = ss_open_cached(dst) 170 let pv: *i64 = sys_mmap(32) as *i64 171 let lv: *i64 = sys_mmap(32) as *i64 172 let out: *u8 = sys_mmap(PM_OUTCAP) 173 var same: i64 = 0 174 var bad: i64 = 0 175 if (h as i64) != 0 { 176 var v: i64 = 0 177 while v < rows { 178 sts_rowkey(v, key) 179 var okrow: i64 = 0 180 if ss_hget(h, key, pv, lv) == 1 { 181 let rp: *u8 = pv[0] as *u8 182 let cols: i64 = nxr_count(rp) 183 let on: i64 = rtv_to_tsv(rp, lv[0], cols, out, PM_OUTCAP) 184 if on == lens[v] { 185 let lp2: *u8 = (buf as i64 + offs[v]) as *u8 186 okrow = 1 187 var k: i64 = 0 188 while k < on { if out[k] != lp2[k] { okrow = 0; k = on } else { k = k + 1 } } 189 } 190 } 191 if okrow == 1 { same = same + 1 } else { bad = bad + 1 } 192 v = v + 1 193 } 194 ss_close(h) 195 } 196 197 o = pm_cat(msg, o, " written=" as *u8); o = rtv_itoa(msg, o, rows) 198 o = pm_cat(msg, o, " verified=" as *u8); o = rtv_itoa(msg, o, same) 199 o = pm_cat(msg, o, " bad=" as *u8); o = rtv_itoa(msg, o, bad) 200 var rc: i64 = 0 201 if bad > 0 { rc = PM_EXIT_MISMATCH } 202 if same != rows { rc = PM_EXIT_MISMATCH } 203 if rc == 0 { o = pm_cat(msg, o, " verdict=GREEN-migrated-and-every-row-renders-back-byte-identical\n" as *u8) } 204 if rc != 0 { o = pm_cat(msg, o, " verdict=RED-VERIFY-FAILED-dst-is-suspect-src-UNTOUCHED\n" as *u8) } 205 sys_write(PM_STDOUT, msg, o) 206 return rc 207}