nx_contentdiff_iterator_candidate_t295.nx source
↩ module page · 514 lines · 24960 B
1// nx_contentdiff_lib.nx -- ONE CONTENT RULER, TWO BUDGETS.
2//
3// THE RULER (lifted verbatim in semantics from nx_contentdiff.nx, whose reasoning stands): every
4// printable run of >= minlen bytes in the LIVE binary must still be findable somewhere in the
5// CANDIDATE's RAW BYTES. A missing run is a content difference requiring review, not proof of
6// destroyed behavior. Conversely, retained strings do not establish behavioral equivalence.
7// Structured ELF comparison below respects validated DWARF string boundaries; the raw API retains its contract.
8//
9// WHY SIZE CANNOT DO THIS JOB, measured twice by the organ this lib is extracted from: SHRINK is not
10// regression (16 live organs had LARGER .prev files yet the smaller live binaries were string
11// SUPERSETS), and GROWTH is not improvement (nx_law_warden GREW 161951 -> 186696 bytes while losing
12// three whole detectors). A gate keyed on bytes is wrong about half the time and cannot tell you which
13// half. Re-measured on the live deploy root 2026-08-06: 22 of 89 staged artifacts with a live
14// counterpart would drop live runs, and several of those are LARGER than what they replace.
15//
16// WHY IT IS A LIB (2026-08-06): the ruler existed only inside nx_contentdiff's main() -- a CLI oracle
17// with no deployed binary, no registry row, and therefore no consumer. The debt that named it
18// (1785531571) asked for it to be wired into /api/promote; nothing could, because there was nothing
19// importable to wire. A ruler that only a main() can reach is not a capability the fleet has.
20//
21// TWO BUDGETS, ONE IMPLEMENTATION (Rule 15). The CLI oracle runs to completion and can afford to check
22// EVERY run; the promote guard runs inside a live deploy call and must bound its work. Those are
23// different BUDGETS, not different rulers -- so the budget is a PARAMETER and the semantics are shared.
24// Two rulers that can disagree about the same question is the defect this file exists to prevent.
25//
26// DIALECT: reachable from nx_mgmt_data -> nx_mgmt_api, so plain-if (NO else), no empty string literals,
27// <=6 params.
28// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
29import "nx_syscalls.nx"
30import "nx_elf_read.nx"
31
32const CDL_PRINT_LO: i64 = 32
33const CDL_PRINT_HI: i64 = 126
34
35// prm[] -- the BUDGET
36const CDL_P_MINLEN: i64 = 0 // shortest printable run that counts as capability
37const CDL_P_MAXSAMPLES: i64 = 1 // 0 = check every run (oracle); N = sample evenly across the file
38const CDL_P_MAXTOKLEN: i64 = 2 // 0 = search the whole run; N = cap the searched prefix
39const CDL_P_SLOTS: i64 = 4
40
41// out[] -- the MEASUREMENT
42const CDL_O_RUNS: i64 = 0 // qualifying runs found in live
43const CDL_O_CHECKED: i64 = 1 // runs actually searched for
44const CDL_O_LOST: i64 = 2 // runs absent from the candidate
45// 6, raised from 4 on 2026-09-03 for the two flavour slots. RAISING THIS IS SAFE FOR EVERY EXISTING
46// CONSUMER: callers size their scratch as 8 * CDL_O_SLOTS, so a larger value only enlarges an allocation,
47// and cdl_lost still writes only slots 0..2 -- the flavour slots are written by cdl_lost_srcpath alone.
48const CDL_O_SLOTS: i64 = 6
49
50func cdl_is_print(c: i64) -> i64 {
51 if c < CDL_PRINT_LO { return 0 }
52 if c > CDL_PRINT_HI { return 0 }
53 return 1
54}
55
56// length of the printable run starting at `start` (0 when that byte is not printable)
57func cdl_runlen(b: *u8, n: i64, start: i64) -> i64 {
58 var k: i64 = 0
59 var go: i64 = 1
60 while go == 1 {
61 if (start + k) >= n { go = 0 }
62 if go == 1 {
63 if cdl_is_print(b[start + k] as i64) == 0 { go = 0 }
64 if go == 1 { k = k + 1 }
65 }
66 }
67 return k
68}
69
70// 1 when the nl bytes at ndl[ns..] appear anywhere in hay[0..hn)
71func cdl_contains(hay: *u8, hn: i64, ndl: *u8, ns: i64, nl: i64) -> i64 {
72 if nl <= 0 { return 1 }
73 if nl > hn { return 0 }
74 let first: u8 = ndl[ns]
75 let last: i64 = hn - nl
76 var i: i64 = 0
77 while i <= last {
78 if hay[i] == first {
79 var k: i64 = 1
80 var ok: i64 = 1
81 while k < nl {
82 if hay[i + k] != ndl[ns + k] { ok = 0; k = nl }
83 if ok == 1 { k = k + 1 }
84 }
85 if ok == 1 { return 1 }
86 }
87 i = i + 1
88 }
89 return 0
90}
91
92// 1 when the two buffers are byte-identical. EQUAL SIZE IS NOT EQUAL CONTENT, and that gap is precisely
93// where a mutation-class defect lives: an ==/!= operator swap preserves the byte count and every printable
94// string, so it is invisible to a size screen AND to the run ruler above. This is the one comparison that
95// separates "a harmless re-stage of the same bytes" from "something changed that nothing else can see".
96func cdl_bytes_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
97 if an != bn { return 0 }
98 var i: i64 = 0
99 while i < an {
100 if a[i] != b[i] { return 0 }
101 i = i + 1
102 }
103 return 1
104}
105
106// how many qualifying runs live carries (NOT deduped -- occurrence counts, as the oracle declares)
107func cdl_count_runs(b: *u8, n: i64, minlen: i64) -> i64 {
108 var cnt: i64 = 0
109 var i: i64 = 0
110 while i < n {
111 let r: i64 = cdl_runlen(b, n, i)
112 if r >= minlen { cnt = cnt + 1 }
113 if r > 0 { i = i + r }
114 if r == 0 { i = i + 1 }
115 }
116 return cnt
117}
118
119// THE MEASURE. Returns loss in permil; fills out[] with the raw counts so a caller can show its work.
120// FAIL-CLOSED: an unreadable/empty candidate against a non-empty live is TOTAL loss (1000), never clean.
121// Sampling (when budgeted) strides EVENLY across the whole binary, so it never reads only the head.
122func cdl_lost(live: *u8, ln: i64, cand: *u8, cn: i64, prm: *i64, out: *i64) -> i64 {
123 out[CDL_O_RUNS] = 0
124 out[CDL_O_CHECKED] = 0
125 out[CDL_O_LOST] = 0
126 if ln <= 0 { return 0 }
127 let minlen: i64 = prm[CDL_P_MINLEN]
128 let total: i64 = cdl_count_runs(live, ln, minlen)
129 out[CDL_O_RUNS] = total
130 if total <= 0 { return 0 }
131 if cn <= 0 { out[CDL_O_CHECKED] = total; out[CDL_O_LOST] = total; return 1000 }
132 let maxs: i64 = prm[CDL_P_MAXSAMPLES]
133 let maxtl: i64 = prm[CDL_P_MAXTOKLEN]
134 var stride: i64 = 1
135 if maxs > 0 { stride = total / maxs }
136 if stride < 1 { stride = 1 }
137 var checked: i64 = 0
138 var lost: i64 = 0
139 var idx: i64 = 0
140 var i: i64 = 0
141 while i < ln {
142 let r: i64 = cdl_runlen(live, ln, i)
143 if r >= minlen {
144 var take: i64 = 0
145 if (idx % stride) == 0 { take = 1 }
146 if maxs > 0 { if checked >= maxs { take = 0 } }
147 if take == 1 {
148 var tl: i64 = r
149 if maxtl > 0 { if tl > maxtl { tl = maxtl } }
150 if cdl_contains(cand, cn, live, i, tl) == 0 { lost = lost + 1 }
151 checked = checked + 1
152 }
153 idx = idx + 1
154 }
155 if r > 0 { i = i + r }
156 if r == 0 { i = i + 1 }
157 }
158 out[CDL_O_CHECKED] = checked
159 out[CDL_O_LOST] = lost
160 if checked <= 0 { return 0 }
161 return (1000 * lost) / checked
162}
163
164// ---- THE FLAVOUR AXIS (2026-09-03) -----------------------------------------------------------------
165// WHY THIS EXISTS, MEASURED THE DAY IT WAS WRITTEN. A --debug build and a release build of the SAME
166// source differ by ~184 permil of printable runs, because -g embeds a `.debug_line` source path per
167// compilation unit. That is roughly 4x the calibrated regression band (ordinary edits measure 8-38
168// permil; every real regression cleared 49+), so this ruler REFUSES any release rebuild of a
169// debug-built binary and reports a catastrophic capability loss THAT DOES NOT EXIST.
170// MEASURED on nx_hostctl: release rebuild lost 332 of 1803 runs (184 permil, RED); the SAME source
171// rebuilt --debug lost 2 of 1803 (1 permil), and those 2 were deliberate deletions. So ~330 of the 332
172// were metadata.
173// ★A DETECTOR WITH FALSE POSITIVES IS WORSE THAN NONE, AND THIS ONE'S FALSE POSITIVE PUSHES THE CALLER
174// STRAIGHT AT `allow_capability_loss=yes` -- i.e. it trains its own users to disarm it.
175// ⛔THE VERDICT IS DELIBERATELY NOT LOOSENED. This is a SEPARATE AXIS: a run can be a source path AND
176// be load-bearing (an organ that prints its own source path), so folding it into the loss count would
177// silently redefine the number every promote is judged against. The partition is ANNOUNCED and the
178// reader decides -- a new bucket that overlaps an existing partition is an axis, never a widened
179// conjunct.
180const CDL_O_SRCPATH: i64 = 3 // of the LOST runs, how many look like a compiler-embedded source path
181const CDL_O_IDENT: i64 = 4 // ... and how many look like a compiler-embedded SYMBOL name
182const CDL_O_SECT: i64 = 5 // ... and how many are ELF/DWARF SECTION names (.debug_info, .shstrtab)
183const CDL_SRC_SFX: *u8 = ".nx"
184const CDL_IDENT_MIN: i64 = 6 // == the ruler's own minimum run length; not an independent knob
185
186// Does this run look like a source path the COMPILER embedded rather than content the program prints?
187// Deliberately NARROW: it must END in ".nx". A message merely mentioning a filename does not qualify,
188// which keeps the class too small to hide a real loss.
189func cdl_is_srcpath(b: *u8, off: i64, len: i64) -> i64 {
190 if len < 4 { return 0 }
191 let e: i64 = off + len
192 if b[e-3] != (46 as u8) { return 0 }
193 if b[e-2] != (110 as u8) { return 0 }
194 if b[e-1] != (120 as u8) { return 0 }
195 return 1
196}
197
198// ⚠THE SOURCE-PATH CLASS ALONE WAS BUILT ON A PREFIX AND REAL DATA REFUTED IT WITHIN THE HOUR. The first
199// cut of this axis classified only runs ending ".nx", because 6 of the 8 lost runs the oracle PRINTS were
200// source paths. The oracle prints 8 of N. Measured on the real case: lost_srcpath=14, lost_other=317.
201// ★I QUOTED "THE PRINTED LIST IS A PREFIX OF ITS OWN COUNT, NEVER THE POPULATION" AND THEN BUILT A
202// DETECTOR ON THE PREFIX. A classifier derived from a sample is a hypothesis about the sample.
203// The build had already named the true class for free: `nxasm_x86: debug-info subprograms=325`, i.e. -g
204// embeds one SYMBOL NAME per function -- and 325 subprograms against 317 unexplained losses is the
205// arithmetic that identifies it. So the axis needs BOTH shapes.
206// Is this run an identifier -- letters, digits and underscore only? That is what a symbol name looks like
207// and what an ordinary message does NOT (messages carry spaces and punctuation). Disjoint from srcpath by
208// construction, since a path carries '/' and '.', so the two classes can never double-count.
209func cdl_is_ident(b: *u8, off: i64, len: i64) -> i64 {
210 if len < CDL_IDENT_MIN { return 0 }
211 var i: i64 = 0
212 while i < len {
213 let c: i64 = b[off + i] as i64
214 var ok: i64 = 0
215 if c == 95 { ok = 1 }
216 if c >= 48 { if c <= 57 { ok = 1 } }
217 if c >= 65 { if c <= 90 { ok = 1 } }
218 if c >= 97 { if c <= 122 { ok = 1 } }
219 if ok == 0 { return 0 }
220 i = i + 1
221 }
222 return 1
223}
224
225// ⚠AND THE PARTITION WAS ONE CLASS SHORT, FOUND BY READING ITS OWN OUTPUT ON A REAL FRONT-DOOR RUN.
226// With srcpath and ident alone, the front door reported lost_other=4 -- and enumerating them showed THREE
227// were `.debug_info`, `.debug_abbrev` and `.shstrtab`: ELF/DWARF SECTION NAMES, which are build metadata
228// exactly like the other two classes but are caught by neither, because they start with '.' so they are
229// not identifier-shaped and do not end in ".nx". ★A PARTITION THAT DUMPS A KNOWN METADATA CLASS INTO
230// "OTHER" INFLATES THE ONE NUMBER A HUMAN IS SUPPOSED TO ADJUDICATE -- the actionable class must contain
231// only what is actually actionable, or it teaches its reader to skim it.
232// Deliberately NARROW: must START with '.', then only lowercase letters, digits, '_' or '.'. A sentence
233// beginning with a full stop does not qualify (it has spaces), and no real message string looks like this.
234func cdl_is_sectname(b: *u8, off: i64, len: i64) -> i64 {
235 if len < CDL_IDENT_MIN { return 0 }
236 if b[off] != (46 as u8) { return 0 }
237 var i: i64 = 1
238 while i < len {
239 let c: i64 = b[off + i] as i64
240 var ok: i64 = 0
241 if c == 95 { ok = 1 }
242 if c == 46 { ok = 1 }
243 if c >= 48 { if c <= 57 { ok = 1 } }
244 if c >= 97 { if c <= 122 { ok = 1 } }
245 if ok == 0 { return 0 }
246 i = i + 1
247 }
248 return 1
249}
250
251// Of the runs LOST from live, how many are source paths and how many are symbol names? Composes
252// cdl_runlen/cdl_contains -- the SAME primitives cdl_lost uses -- so the two can never disagree about what
253// "lost" means. Writes both counts into out[] so a caller can reconcile the partition, and returns their
254// SUM (the runs the build flavour can account for).
255func cdl_lost_srcpath(live: *u8, ln: i64, cand: *u8, cn: i64, minlen: i64, out: *i64) -> i64 {
256 var np: i64 = 0
257 var nd: i64 = 0
258 var ns: i64 = 0
259 var i: i64 = 0
260 while i < ln {
261 let r: i64 = cdl_runlen(live, ln, i)
262 if r >= minlen {
263 if cdl_contains(cand, cn, live, i, r) == 0 {
264 if cdl_is_srcpath(live, i, r) == 1 { np = np + 1 } else {
265 if cdl_is_ident(live, i, r) == 1 { nd = nd + 1 } else {
266 if cdl_is_sectname(live, i, r) == 1 { ns = ns + 1 }
267 }
268 }
269 }
270 }
271 if r > 0 { i = i + r }
272 if r == 0 { i = i + 1 }
273 }
274 if (out as i64) != 0 {
275 out[CDL_O_SRCPATH] = np
276 out[CDL_O_IDENT] = nd
277 out[CDL_O_SECT] = ns
278 }
279 return np + nd + ns
280}
281
282// ---- THE SYMMETRY CLASS ----------------------------------------------------------------------------
283// NOT A NEW IDEA AND NOT A NEW RULER: nx_staging_guard has run cdl_lost with its arguments SWAPPED since
284// 2026-08-06, with the reasoning in its own comment ("A LOSS DETECTOR THAT COUNTS ONLY WHAT VANISHED
285// CANNOT TELL A RENAME FROM A REMOVAL"). What was missing is that the ORACLE seats run BY HAND never
286// exposed it, so the measurement had to be rediscovered from scratch -- a fix wired into one consumer
287// and not its sibling, where the sibling is the verb whose whole purpose is to tell you what to fix.
288// This just names the four outcomes so both consumers can say the same word.
289// ⛔THRESHOLD-FREE ON PURPOSE. There is no principled cutoff for "asymmetric enough", and inventing one
290// would be a magic number gating a safety decision. The states key only on ZERO vs NONZERO; the two
291// permils are PUBLISHED so the reader judges the degree. Same discipline as nx_spendgate: a ruler that
292// cannot separate novel from duplicate publishes the list, not a verdict.
293const CDL_SYM_IDENTICAL: i64 = 0 // neither side has a run the other lacks
294const CDL_SYM_LOSS: i64 = 1 // only the candidate is missing runs -> a genuine one-way LOSS
295const CDL_SYM_GAIN: i64 = 2 // only live is missing runs -> the candidate is a strict SUPERSET
296const CDL_SYM_BOTH: i64 = 3 // both directions lose -> rename / re-encode / build-flavour change
297
298func cdl_symclass(lost_fwd: i64, lost_rev: i64) -> i64 {
299 if lost_fwd <= 0 { if lost_rev <= 0 { return CDL_SYM_IDENTICAL } return CDL_SYM_GAIN }
300 if lost_rev <= 0 { return CDL_SYM_LOSS }
301 return CDL_SYM_BOTH
302}
303
304func cdl_symname(s: i64) -> *u8 {
305 if s == CDL_SYM_IDENTICAL { return "IDENTICAL-CONTENT" as *u8 }
306 if s == CDL_SYM_LOSS { return "ONE-WAY-LOSS (the candidate drops runs and adds none -- a REGRESSION)" as *u8 }
307 if s == CDL_SYM_GAIN { return "ONE-WAY-GAIN (the candidate is a strict SUPERSET -- a gift, not litter)" as *u8 }
308 return "BIDIRECTIONAL (runs moved BOTH ways -- a rename, a re-encode or a build-flavour change, NOT a plain loss; read the two permils and run a matched-vintage control)" as *u8
309}
310
311// Additive structured comparison. Callers must reject nonzero state before reading counts.
312const CDS_INVALID:i64=-201
313const CDS_UNKNOWN:i64=-202
314const CDS_RESOURCE:i64=-203
315const CDS_FILE:i64=1
316const CDS_DIR:i64=2
317const CDS_END:i64=3
318struct NxCdStructured {buf:*u8,bytes:i64,marks:*u8,at:i64,end:i64,error:i64,files:i64,sections:i64}
319func cds_init(c:*NxCdStructured,b:*u8,n:i64)->i64{
320 c.buf=b;c.bytes=n;c.marks=0 as *u8;c.at=0;c.end=0;c.error=0;c.files=0;c.sections=0
321 if n<=0{return CDS_INVALID};if (b as i64)<=0{return CDS_INVALID}
322 c.marks=sys_mmap_try(n);if (c.marks as i64)<=0{return CDS_RESOURCE};return 0
323}
324func cds_close(c:*NxCdStructured)->i64{if (c.marks as i64)>0{sys_munmap_direct(c.marks,c.bytes)};c.marks=0 as *u8;return 0}
325func cds_span(n:i64,o:i64,z:i64)->i64{if o<0||z<0||o>n{return 0};if z>n-o{return 0};return 1}
326func cds_byte(c:*NxCdStructured)->i64{
327 if c.error!=0{return -1};if c.at>=c.end{c.error=CDS_INVALID;return -1}
328 let v:i64=c.buf[c.at] as i64;c.at=c.at+1;return v
329}
330// Checked unsigned LEB restricted to representable nonnegative i64 counts/indexes.
331func cds_uleb(c:*NxCdStructured)->i64{
332 var v:i64=0;var shift:i64=0
333 while c.error==0{
334 let b:i64=cds_byte(c);if c.error!=0{return -1}
335 let low:i64=b&127
336 if shift>=63{c.error=CDS_INVALID;return -1}
337 v=v|(low<<shift)
338 if (b&128)==0{return v};shift=shift+7
339 }
340 return -1
341}
342func cds_string(c:*NxCdStructured,kind:i64)->i64{
343 let begin:i64=c.at
344 while c.at<c.end{
345 let v:i64=cds_byte(c);if c.error!=0{return c.error}
346 if v==0{if c.at-1==begin{c.error=CDS_UNKNOWN;return c.error};c.marks[begin]=kind as u8;c.marks[c.at-1]=CDS_END as u8;return 0}
347 if v<32||v>126{c.error=CDS_UNKNOWN;return c.error}
348 }
349 c.error=CDS_INVALID;return c.error
350}
351func cds_line(c:*NxCdStructured,off:i64,size:i64)->i64{
352 let sectionend:i64=off+size;var unit:i64=off
353 while unit<sectionend{
354 if cds_span(sectionend,unit,12)==0{return CDS_INVALID}
355 let length:i64=nx_elf_get_u32_le(c.buf,unit)
356 if length>=0xfffffff0{return CDS_UNKNOWN}
357 if cds_span(sectionend,unit+4,length)==0{return CDS_INVALID}
358 let unitend:i64=unit+4+length
359 if length<8{return CDS_INVALID}
360 if nx_elf_get_u16_le(c.buf,unit+4)!=5{return CDS_UNKNOWN}
361 if c.buf[unit+6]!=(8 as u8)||c.buf[unit+7]!=(0 as u8){return CDS_UNKNOWN}
362 let hlen:i64=nx_elf_get_u32_le(c.buf,unit+8)
363 if cds_span(unitend,unit+12,hlen)==0{return CDS_INVALID}
364 c.at=unit+12;c.end=c.at+hlen
365 if hlen<6{return CDS_INVALID}
366 let mininst:i64=cds_byte(c);let maxops:i64=cds_byte(c);let stmt:i64=cds_byte(c)
367 cds_byte(c);let range:i64=cds_byte(c);let opbase:i64=cds_byte(c)
368 if mininst==0||maxops==0||stmt>1||range==0||opbase==0{return CDS_INVALID}
369 if cds_span(c.end,c.at,opbase-1)==0{return CDS_INVALID};c.at=c.at+opbase-1
370 // Accepted schema is explicit: inline directory path; inline file path + ULEB directory index.
371 if cds_byte(c)!=1{return CDS_UNKNOWN}
372 if cds_uleb(c)!=1{return CDS_UNKNOWN};if cds_uleb(c)!=8{return CDS_UNKNOWN}
373 let dirs:i64=cds_uleb(c);if c.error!=0{return c.error}
374 if dirs>c.end-c.at{return CDS_INVALID}
375 var i:i64=0;while i<dirs{if cds_string(c,CDS_DIR)!=0{return c.error};i=i+1}
376 if cds_byte(c)!=2{return CDS_UNKNOWN}
377 if cds_uleb(c)!=1{return CDS_UNKNOWN};if cds_uleb(c)!=8{return CDS_UNKNOWN}
378 if cds_uleb(c)!=2{return CDS_UNKNOWN};if cds_uleb(c)!=15{return CDS_UNKNOWN}
379 let files:i64=cds_uleb(c);if c.error!=0{return c.error}
380 if files>(c.end-c.at)/2{return CDS_INVALID}
381 i=0;while i<files{
382 if cds_string(c,CDS_FILE)!=0{return c.error}
383 let directory:i64=cds_uleb(c);if c.error!=0{return c.error}
384 if directory>=dirs{return CDS_INVALID};c.files=c.files+1;i=i+1
385 }
386 if c.at!=c.end{return CDS_INVALID}
387 unit=unitend
388 }
389 if unit!=sectionend{return CDS_INVALID};return 0
390}
391// Reuses existing ELF LE primitives only AFTER proving each enclosing extent.
392func cds_elf(c:*NxCdStructured)->i64{
393 let b:*u8=c.buf;let n:i64=c.bytes
394 if n<64{return CDS_INVALID}
395 if b[0]!=(127 as u8)||b[1]!=(69 as u8)||b[2]!=(76 as u8)||b[3]!=(70 as u8){return CDS_UNKNOWN}
396 if b[4]!=(2 as u8)||b[5]!=(1 as u8)||b[6]!=(1 as u8){return CDS_UNKNOWN}
397 if nx_elf_get_u16_le(b,52)!=64{return CDS_UNKNOWN}
398 let table:i64=nx_elf_get_u64_le(b,40);let stride:i64=nx_elf_get_u16_le(b,58)
399 let count:i64=nx_elf_get_u16_le(b,60);let names:i64=nx_elf_get_u16_le(b,62)
400 if count==0{return CDS_UNKNOWN};if stride!=64{return CDS_UNKNOWN}
401 if names>=count{return CDS_INVALID}
402 if cds_span(n,table,count*stride)==0{return CDS_INVALID}
403 let sn:i64=table+names*stride
404 if nx_elf_get_u32_le(b,sn+4)!=3{return CDS_INVALID}
405 let noff:i64=nx_elf_get_u64_le(b,sn+24);let nsize:i64=nx_elf_get_u64_le(b,sn+32)
406 if cds_span(n,noff,nsize)==0{return CDS_INVALID}
407 var i:i64=0
408 while i<count{
409 let s:i64=table+i*stride;let name:i64=nx_elf_get_u32_le(b,s)
410 if name>=nsize{return CDS_INVALID}
411 var z:i64=0;while name+z<nsize{if b[noff+name+z]==0 as u8{break};z=z+1}
412 if name+z>=nsize{return CDS_INVALID}
413 let off:i64=nx_elf_get_u64_le(b,s+24);let size:i64=nx_elf_get_u64_le(b,s+32)
414 let ty:i64=nx_elf_get_u32_le(b,s+4)
415 if ty!=8{if cds_span(n,off,size)==0{return CDS_INVALID}}
416 if z==11{
417 if cdl_bytes_eq(b+noff+name,z,".debug_line",11)==1{
418 if ty!=1{return CDS_UNKNOWN}
419 if (nx_elf_get_u64_le(b,s+8)&2050)!=0{return CDS_UNKNOWN}
420 if off<64{return CDS_INVALID}
421 if off<table+count*stride{if table<off+size{return CDS_INVALID}}
422 var j:i64=0;while j<count{
423 if j!=i{let other:i64=table+j*stride;let ot:i64=nx_elf_get_u32_le(b,other+4)
424 if ot!=8{let oo:i64=nx_elf_get_u64_le(b,other+24);let oz:i64=nx_elf_get_u64_le(b,other+32)
425 if cds_span(n,oo,oz)==0{return CDS_INVALID}
426 if oz>0{if off<oo+oz{if oo<off+size{return CDS_INVALID}}}
427 }
428 };j=j+1
429 }
430 let rc:i64=cds_line(c,off,size);if rc!=0{return rc};c.sections=c.sections+1
431 }
432 }
433 i=i+1
434 }
435 return 0
436}
437func cds_run(c:*NxCdStructured,start:i64)->i64{
438 var n:i64=0;while start+n<c.bytes{
439 if n>0{if c.marks[start+n]!=0 as u8{break}}
440 if cdl_is_print(c.buf[start+n] as i64)==0{break};n=n+1
441 };return n
442}
443func cds_file_present(a:*NxCdStructured,at:i64,n:i64,b:*NxCdStructured)->i64{
444 var j:i64=0;while j<b.bytes{
445 if b.marks[j]==CDS_FILE as u8{
446 let z:i64=cdl_runlen(b.buf,b.bytes,j)
447 if z==n{if cdl_bytes_eq(a.buf+at,n,b.buf+j,z)==1{return 1}}
448 };j=j+1
449 };return 0
450}
451// out adds structured table evidence separate from raw content evidence.
452struct NxCdResult {state:i64,runs:i64,checked:i64,lost:i64,source_removed:i64,source_added:i64}
453
454struct NxCdSpan {offset:i64,length:i64,kind:i64}
455// Single segmented traversal for measurement, classification and enumeration.
456// Requires successfully validated contexts from cds_elf/cds_measure.
457func cds_next_span(c:*NxCdStructured,cursor:*i64,s:*NxCdSpan)->i64{
458 while cursor[0]<c.bytes{
459 let at:i64=cursor[0];let n:i64=cds_run(c,at)
460 if n>0{cursor[0]=at+n;s.offset=at;s.length=n;s.kind=c.marks[at] as i64;return 1}
461 cursor[0]=at+1
462 };return 0
463}
464func cds_span_missing(a:*NxCdStructured,b:*NxCdStructured,s:*NxCdSpan,maxlen:i64)->i64{
465 var n:i64=s.length;if maxlen>0{if n>maxlen{n=maxlen}}
466 return 1-cdl_contains(b.buf,b.bytes,a.buf,s.offset,n)
467}
468func cds_source_missing(a:*NxCdStructured,b:*NxCdStructured,s:*NxCdSpan)->i64{
469 if s.kind!=CDS_FILE{return 0};return 1-cds_file_present(a,s.offset,s.length,b)
470}
471const CDS_CLASS_SOURCE:i64=1
472const CDS_CLASS_IDENT:i64=2
473const CDS_CLASS_SECTION:i64=3
474const CDS_CLASS_OTHER:i64=4
475func cds_span_class(a:*NxCdStructured,s:*NxCdSpan)->i64{
476 if s.kind==CDS_FILE{return CDS_CLASS_SOURCE}
477 if cdl_is_srcpath(a.buf,s.offset,s.length)==1{return CDS_CLASS_SOURCE}
478 if cdl_is_ident(a.buf,s.offset,s.length)==1{return CDS_CLASS_IDENT}
479 if cdl_is_sectname(a.buf,s.offset,s.length)==1{return CDS_CLASS_SECTION}
480 return CDS_CLASS_OTHER
481}
482// Exact permil without the potentially overflowing lost*1000 product.
483func cds_permil(lost:i64,checked:i64)->i64{
484 if lost<0||checked<0||lost>checked{return CDS_INVALID};if checked==0{return 0}
485 var remainder:i64=0;var value:i64=0;var i:i64=0
486 while i<1000{
487 if remainder>=checked-lost{remainder=remainder-(checked-lost);value=value+1}else{remainder=remainder+lost};i=i+1
488 };return value
489}
490
491func cds_measure(a:*NxCdStructured,b:*NxCdStructured,prm:*i64,out:*NxCdResult)->i64{
492 out.state=0;out.runs=0;out.checked=0;out.lost=0;out.source_removed=0;out.source_added=0
493 if prm[CDL_P_MINLEN]<=0||prm[CDL_P_MAXSAMPLES]<0||prm[CDL_P_MAXTOKLEN]<0{out.state=CDS_INVALID;return out.state}
494 let ar:i64=cds_elf(a);if ar!=0{out.state=ar;return ar}
495 let br:i64=cds_elf(b);if br!=0{out.state=br;return br}
496 var i:i64=0;var span:NxCdSpan
497 while cds_next_span(a,&i,&span)==1{
498 if span.length>=prm[CDL_P_MINLEN]{out.runs=out.runs+1}
499 out.source_removed=out.source_removed+cds_source_missing(a,b,&span)
500 }
501 i=0;while cds_next_span(b,&i,&span)==1{out.source_added=out.source_added+cds_source_missing(b,a,&span)}
502 var stride:i64=1;let maxs:i64=prm[CDL_P_MAXSAMPLES]
503 if maxs>0{stride=out.runs/maxs};if stride<1{stride=1}
504 var idx:i64=0;i=0
505 while cds_next_span(a,&i,&span)==1{
506 if span.length>=prm[CDL_P_MINLEN]{
507 var take:i64=0;if idx%stride==0{take=1};if maxs>0{if out.checked>=maxs{take=0}}
508 if take==1{
509 out.lost=out.lost+cds_span_missing(a,b,&span,prm[CDL_P_MAXTOKLEN]);out.checked=out.checked+1
510 };idx=idx+1
511 }
512 }
513 return 0
514}