code wiki / (root) / nx_contentdiff.nx

nx_contentdiff.nx source

↩ module page · 287 lines · 15029 B

1// nx_contentdiff.nx -- THE PROMOTE RULER. Answers one question: does the candidate binary still contain 2// everything the LIVE binary contains? 3// 4// WHY (debts 1785452162, 1785526315, 1785526809): the ecosystem has ~703 deployed organs awaiting a 5// rebuild, and the gate guarding that queue was BYTE SIZE. Size is uninformative IN BOTH DIRECTIONS, 6// proven twice by measurement: 7// SHRINK is not regression -- 16 live organs had LARGER .prev files, yet the smaller live binaries 8// were string SUPERSETS (lost_from_live=0). Restoring the bigger ones would have BEEN the regression. 9// GROWTH is not improvement -- nx_law_warden rebuilt GREW 161951 -> 186696 bytes while losing three 10// whole detectors (laws_measured 11 -> 8, enforced_permil 1000 -> 727). 11// A gate keyed on bytes is therefore wrong about half the time and cannot tell you which half. 12// 13// THE RULER: printable content. Every printable run of >= CD_MINLEN bytes in the LIVE binary must still 14// be findable somewhere in the candidate. Anything missing is capability the rebuild would DESTROY. 15// Searching the candidate's RAW BYTES (not a re-extracted string set) is deliberate and stricter: a 16// string that survived but got tokenized differently still counts as present, so a MISS is a real miss. 17// 18// ★2026-08-06 -- THE RULER MOVED OUT, THE ORACLE STAYED. This organ had NO DEPLOYED BINARY and no 19// registry row, so the check it specifies had never actually run anywhere: debt 1785531571 asked for it 20// to be wired into /api/promote and nothing could be, because the measure lived inside this main() with 21// nothing importable to wire. It now lives in nx_contentdiff_lib (cdl_lost) and is called BOTH here and 22// by the staging guard inside md_promote_staged. This file keeps its exact output contract; it simply 23// no longer owns a private copy of the arithmetic. 24// 25// FAIL-CLOSED: unreadable input is RED, never "clean". Verdict GREEN iff lost_from_live == 0. 26// NOT DEDUPED, and it says so in the output: runs are counted as encountered, so the counts are 27// occurrence counts and lost>0 is the signal -- declaring that beats a silent, prettier number. 28// THE ORACLE SPENDS THE FULL BUDGET (every run, whole run) -- it runs to completion and can afford it. 29// 30// nx_contentdiff <live-elf> <candidate-elf> [all|positive-display-limit] 31// Optional display depth changes evidence visibility only; counts and verdict remain shared. 32// 33// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 34import "nx_syscalls.nx" 35import "nx_gate_verdict.nx" 36import "_hdl_build/nx_contentdiff_lib.nx" 37 38const CD_CAP: i64 = 4194304 39const CD_MINLEN: i64 = 6 40const CD_SHOW: i64 = 8 41const CD_SHOW_ALL: i64 = 0 - 1 42const CD_SHOW_BAD: i64 = 0 - 2 43const CD_DECIMAL_BASE: i64 = 10 44const CD_I64_MAX: i64 = 9223372036854775807 45const CD_DIGIT_ZERO: i64 = 48 46const CD_DIGIT_NINE: i64 = 57 47 48// Zero is refused: hiding every detail would make existing "no runs" messages misleading. 49func cd_display_arg(p: *u8) -> i64 { 50 if p[0] == (97 as u8) { if p[1] == (108 as u8) { if p[2] == (108 as u8) { if p[3] == (0 as u8) { return CD_SHOW_ALL } } } } 51 var i: i64 = 0; var value: i64 = 0 52 while p[i] != (0 as u8) { 53 let c: i64 = p[i] as i64 54 if c < CD_DIGIT_ZERO { return CD_SHOW_BAD } 55 if c > CD_DIGIT_NINE { return CD_SHOW_BAD } 56 let digit: i64 = c - CD_DIGIT_ZERO 57 if value > (CD_I64_MAX - digit) / CD_DECIMAL_BASE { return CD_SHOW_BAD } 58 value = value * CD_DECIMAL_BASE + digit 59 i = i + 1 60 } 61 if value <= 0 { return CD_SHOW_BAD } 62 return value 63} 64 65func cd_slurp(path: *u8, buf: *u8, cap: i64) -> i64 { 66 let fd: i64 = sys_openat_rd(path) 67 if fd < 0 { return 0 - 1 } 68 var tot: i64 = 0 69 var go: i64 = 1 70 while go == 1 { 71 let r: i64 = sys_read(fd, ((buf as i64) + tot) as *u8, cap - tot) 72 if r <= 0 { go = 0 } else { tot = tot + r } 73 if tot >= cap { go = 0 } 74 } 75 sys_close(fd) 76 return tot 77} 78 79// DISPLAY ONLY -- names the first CD_SHOW lost runs so a reader sees WHAT would be destroyed, not just 80// how many. Uses the SAME shared primitives as the count, so the two can never disagree about what 81// "lost" means; the authoritative numbers still come from cdl_lost. 82// THE ACTIONABLE CLASS, LISTED SEPARATELY (2026-09-03). cd_show_lost prints the first CD_SHOW runs of 83// ALL losses, and on any cross-toolchain comparison those are dominated by build-flavour metadata: a real 84// front-door comparison printed 8 source paths while the 4 losses that needed adjudication were past the 85// cap and UNREACHABLE. ★A LIST THAT NAMES EIGHT LOSSES THAT DO NOT MATTER WHILE UNABLE TO NAME THE FOUR 86// THAT DO IS THE ONE THING THIS ORACLE EXISTS TO PREVENT -- it is the count-without-a-worklist defect 87// wearing a worklist. The partition already computes the class; the list simply never used it. 88// Prints ONLY runs that are neither a source path nor a symbol name, i.e. the class the flavour axis 89// cannot explain away and a human must therefore read. Additive: the all-losses list above is untouched. 90func cd_show_other(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { 91 return cd_show_other_limit(a, an, b, bn, CD_SHOW) 92} 93func cd_show_other_limit(a: *u8, an: i64, b: *u8, bn: i64, limit: i64) -> i64 { 94 var shown: i64 = 0 95 var i: i64 = 0 96 while i < an { 97 let rl: i64 = cdl_runlen(a, an, i) 98 if rl >= CD_MINLEN { 99 if shown < limit { 100 if cdl_contains(b, bn, a, i, rl) == 0 { 101 if cdl_is_srcpath(a, i, rl) == 0 { 102 if cdl_is_ident(a, i, rl) == 0 { if cdl_is_sectname(a, i, rl) == 0 { 103 shown = shown + 1 104 gv_puts(" LOST-OTHER: " as *u8) 105 sys_write(1, ((a as i64) + i) as *u8, rl) 106 gv_puts("\n" as *u8) 107 } } 108 } 109 } 110 } 111 } 112 if rl > 0 { i = i + rl } 113 if rl == 0 { i = i + 1 } 114 } 115 return shown 116} 117 118// AND THE OTHER HALF OF THE SYMMETRIC PASS: NAME WHAT THE CANDIDATE ADDS, not just how many. 119// `gained_by_candidate` shipped as a bare COUNT, which is the estate's own count-without-a-worklist 120// defect -- and it is the field that answers PROVENANCE ("is this binary really built from my source?"), 121// so a reader who needs it most had to re-run the whole ruler with the arguments swapped to see a list. 122// ★A COUNTER IS THE DEFAULT AND A LIST IS AN AFTERTHOUGHT; ADD THE ARRAY IN THE SAME EDIT AS THE COUNTER. 123func cd_show_gained(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { 124 return cd_show_gained_limit(a, an, b, bn, CD_SHOW) 125} 126func cd_show_gained_limit(a: *u8, an: i64, b: *u8, bn: i64, limit: i64) -> i64 { 127 var shown: i64 = 0 128 var i: i64 = 0 129 while i < bn { 130 let rl: i64 = cdl_runlen(b, bn, i) 131 if rl >= CD_MINLEN { 132 if shown < limit { 133 if cdl_contains(a, an, b, i, rl) == 0 { 134 if cdl_is_srcpath(b, i, rl) == 0 { 135 if cdl_is_ident(b, i, rl) == 0 { if cdl_is_sectname(b, i, rl) == 0 { 136 shown = shown + 1 137 gv_puts(" GAINED: " as *u8) 138 sys_write(1, ((b as i64) + i) as *u8, rl) 139 gv_puts("\n" as *u8) 140 } } 141 } 142 } 143 } 144 } 145 if rl > 0 { i = i + rl } 146 if rl == 0 { i = i + 1 } 147 } 148 return shown 149} 150 151func cd_show_lost(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { 152 return cd_show_lost_limit(a, an, b, bn, CD_SHOW) 153} 154func cd_show_lost_limit(a: *u8, an: i64, b: *u8, bn: i64, limit: i64) -> i64 { 155 var shown: i64 = 0 156 var i: i64 = 0 157 while i < an { 158 let rl: i64 = cdl_runlen(a, an, i) 159 if rl >= CD_MINLEN { 160 if shown < limit { 161 if cdl_contains(b, bn, a, i, rl) == 0 { 162 shown = shown + 1 163 gv_puts(" LOST: " as *u8) 164 sys_write(1, ((a as i64) + i) as *u8, rl) 165 gv_puts("\n" as *u8) 166 } 167 } 168 } 169 if rl > 0 { i = i + rl } 170 if rl == 0 { i = i + 1 } 171 } 172 return shown 173} 174 175func cd_display_summary(label: *u8, shown: i64, total: i64) -> i64 { 176 gv_puts("display_class=" as *u8); gv_puts(label) 177 gv_puts(" shown=" as *u8); gv_num(shown) 178 gv_puts(" total=" as *u8); gv_num(total) 179 gv_puts(" omitted=" as *u8); gv_num(total - shown) 180 gv_puts("\n" as *u8) 181 return 0 182} 183 184 185struct NxCdDisplay {total:i64,shown:i64,source:i64,ident:i64,section:i64,other:i64} 186const CD_STRUCT_ALL:i64=0 187const CD_STRUCT_OTHER:i64=1 188const CD_STRUCT_SOURCE:i64=2 189func cd_structured_names(a:*NxCdStructured,b:*NxCdStructured,label:*u8,mode:i64,limit:i64,out:*NxCdDisplay)->i64{ 190 out.total=0;out.shown=0;out.source=0;out.ident=0;out.section=0;out.other=0 191 var pos:i64=0;var span:NxCdSpan 192 while cds_next_span(a,&pos,&span)==1{ 193 var missing:i64=0 194 if mode==CD_STRUCT_SOURCE{missing=cds_source_missing(a,b,&span)}else{ 195 if span.length>=CD_MINLEN{missing=cds_span_missing(a,b,&span,0)} 196 } 197 if missing==1{ 198 let klass:i64=cds_span_class(a,&span) 199 if mode!=CD_STRUCT_OTHER||klass==CDS_CLASS_OTHER{ 200 out.total=out.total+1 201 if klass==CDS_CLASS_SOURCE{out.source=out.source+1}else{ 202 if klass==CDS_CLASS_IDENT{out.ident=out.ident+1}else{if klass==CDS_CLASS_SECTION{out.section=out.section+1}else{out.other=out.other+1}} 203 } 204 if out.shown<limit{ 205 gv_puts(" ");gv_puts(label);gv_puts(": ");sys_write(1,(a.buf as i64+span.offset) as *u8,span.length);gv_puts("\n") 206 out.shown=out.shown+1 207 } 208 } 209 } 210 };return out.shown 211} 212func cd_structured_error(state:i64,reason:*u8)->i64{ 213 gv_puts("comparison_kind=elf-structured-v1 structured_state=");gv_num(state) 214 gv_puts(" verdict=RED rule=");gv_puts(reason);gv_puts(" lost_from_live=-1 source_removed=-1 evidence=unqualified\n");return 1 215} 216// Complete read-only file mappings; this path never compares a fixed-size prefix. 217func cd_structured_paths(livep:*u8,candp:*u8,display:i64)->i64{ 218 return cd_structured_paths_result(livep,candp,display,0 as *NxCdResult) 219} 220// Typed result lets native workflows consume evidence without parsing console text. 221// state remains invalid until complete comparison and enumeration agree. 222func cd_structured_paths_result(livep:*u8,candp:*u8,display:i64,measured:*NxCdResult)->i64{ 223 if (measured as i64)!=0{ 224 measured.state=CDS_INVALID;measured.runs=-1;measured.checked=-1;measured.lost=-1 225 measured.source_removed=-1;measured.source_added=-1 226 } 227 var an:i64=0;var bn:i64=0 228 let a:*u8=sys_map_file(livep,&an) 229 if (a as i64)<=0{return cd_structured_error(CDS_INVALID,"live-unreadable")} 230 let b:*u8=sys_map_file(candp,&bn) 231 if (b as i64)<=0{sys_munmap_direct(a,an);return cd_structured_error(CDS_INVALID,"candidate-unreadable")} 232 gv_puts("=== NX-CONTENTDIFF live=");gv_puts(livep);gv_puts(" candidate=");gv_puts(candp);gv_puts(" ===\n") 233 var x:NxCdStructured;var y:NxCdStructured 234 let xi:i64=cds_init(&x,a,an);let yi:i64=cds_init(&y,b,bn) 235 let prm:*i64=sys_mmap_try(CDL_P_SLOTS*__size_of(i64)) as *i64 236 var rc:i64=xi;if rc==0{rc=yi};if (prm as i64)<=0{rc=CDS_RESOURCE} 237 var forward:NxCdResult;var reverse:NxCdResult 238 if rc==0{ 239 prm[CDL_P_MINLEN]=CD_MINLEN;prm[CDL_P_MAXSAMPLES]=0;prm[CDL_P_MAXTOKLEN]=0 240 rc=cds_measure(&x,&y,prm,&forward) 241 if rc==0{rc=forward.state} 242 if rc==0{rc=cds_measure(&y,&x,prm,&reverse);if rc==0{rc=reverse.state}} 243 } 244 var result:i64=1 245 if rc!=0{if (measured as i64)!=0{measured.state=rc};cd_structured_error(rc,"structured-comparison-refused")}else{ 246 var limit:i64=display;if display==CD_SHOW_ALL{limit=CD_I64_MAX} 247 var loss:NxCdDisplay;var other:NxCdDisplay;var gain:NxCdDisplay;var sources:NxCdDisplay;var added:NxCdDisplay 248 cd_structured_names(&x,&y,"LOST",CD_STRUCT_ALL,limit,&loss) 249 cd_structured_names(&x,&y,"LOST-OTHER",CD_STRUCT_OTHER,limit,&other) 250 cd_structured_names(&y,&x,"GAINED",CD_STRUCT_OTHER,limit,&gain) 251 cd_structured_names(&x,&y,"SOURCE-REMOVED",CD_STRUCT_SOURCE,limit,&sources) 252 cd_structured_names(&y,&x,"SOURCE-ADDED",CD_STRUCT_SOURCE,limit,&added) 253 var consistent:i64=1 254 if loss.total!=forward.lost||sources.total!=forward.source_removed||added.total!=forward.source_added{consistent=0} 255 if other.total!=loss.other||loss.source+loss.ident+loss.section+loss.other!=loss.total{consistent=0} 256 if consistent==0{cd_structured_error(CDS_INVALID,"count-enumeration-disagreement")}else{ 257 if (measured as i64)!=0{ 258 measured.state=0;measured.runs=forward.runs;measured.checked=forward.checked 259 measured.lost=forward.lost;measured.source_removed=forward.source_removed;measured.source_added=forward.source_added 260 } 261 gv_puts("\ncomparison_kind=elf-structured-v1 structured_state=0\nlive_bytes=");gv_num(an);gv_puts(" candidate_bytes=");gv_num(bn) 262 gv_puts("\nruns_scanned=");gv_num(forward.runs);gv_puts(" lost_from_live=");gv_num(forward.lost);gv_puts(" lost_permil=");gv_num(cds_permil(forward.lost,forward.checked));gv_puts(" minlen=");gv_num(CD_MINLEN) 263 gv_puts("\ndeduped=0 (occurrence counts, declared not hidden)\ncandidate_runs=");gv_num(reverse.runs);gv_puts(" gained_by_candidate=");gv_num(reverse.lost);gv_puts(" gained_permil=");gv_num(cds_permil(reverse.lost,reverse.checked)) 264 gv_puts("\nsymmetry=");gv_puts(cdl_symname(cdl_symclass(forward.lost,reverse.lost))) 265 gv_puts("\nlost_srcpath=");gv_num(loss.source);gv_puts(" lost_symbolname=");gv_num(loss.ident);gv_puts(" lost_sectname=");gv_num(loss.section);gv_puts(" lost_other=");gv_num(loss.other) 266 gv_puts("\nsource_removed=");gv_num(forward.source_removed);gv_puts(" source_added=");gv_num(forward.source_added);gv_puts("\n") 267 cd_display_summary("LOST",loss.shown,loss.total);cd_display_summary("LOST-OTHER",other.shown,other.total);cd_display_summary("GAINED",gain.shown,gain.total) 268 cd_display_summary("SOURCE-REMOVED",sources.shown,sources.total);cd_display_summary("SOURCE-ADDED",added.shown,added.total) 269 if forward.lost==0&&forward.source_removed==0{ 270 gv_puts("NX-CONTENTDIFF verdict=GREEN (qualified structured content and source paths retained; behavior unmeasured)\n");result=0 271 }else{gv_puts("NX-CONTENTDIFF verdict=RED (content or source-path loss requires review; counts do not prove behavior)\n")} 272 } 273 } 274 cds_close(&x);cds_close(&y) 275 if (prm as i64)>0{sys_munmap_direct(prm as *u8,CDL_P_SLOTS*__size_of(i64))} 276 sys_munmap_direct(a,an);sys_munmap_direct(b,bn);return result 277} 278func main(argc:i64,argv:*i64)->i64{ 279 if argc<3{gv_puts("usage: nx_contentdiff <live-elf> <candidate-elf> [all|positive-display-limit]\n");return 2} 280 var display:i64=CD_SHOW 281 if argc>3{ 282 display=cd_display_arg(argv[3] as *u8);if argc>4{display=CD_SHOW_BAD} 283 if display==CD_SHOW_BAD{gv_puts("error=invalid-display-limit expected=all-or-positive-decimal\n");return 2} 284 gv_puts("display_limit=");gv_puts(argv[3] as *u8);gv_puts("\n") 285 } 286 return cd_structured_paths(argv[1] as *u8,argv[2] as *u8,display) 287}