nx_wpt_spine.nx source
↩ module page · 243 lines · 9710 B
1// nx_wpt_spine.nx -- LIB: the shared TEST-TELEMETRY SPINE (2026-08-19). Extracted from
2// nx_wpt_runner v3 the day nx_wpt_reftest became its second consumer (the DRY law: the second
3// copy is where drift starts). One implementation of:
4// - engine identity (sha256 of the calling binary itself, via /proc/self/exe),
5// - deterministic corpus enumeration (sorted names) + content hash (name+NUL+bytes, sorted order),
6// - the TSV history scan (newest row matching dir[/engine][/corpus]) + column reads,
7// - the 16-hex truncated digest form (64 bits, DECLARED truncation),
8// - history append.
9// Row schema (documented once, here, OTel CI/CD mapped): epoch_s(cicd.pipeline.run start)
10// col1=suite(test.suite name) col2=engine_sha16(service.version) col3=corpus_sha16(vcs.ref analog)
11// then consumer-declared counter columns, dur_ms, verdict LAST.
12// license_tier: ORIGINAL
13
14import "nx_syscalls.nx"
15import "nx_sha256.nx"
16
17// ---- THE FIELD SEPARATOR, ONE OWNER (2026-08-27) ------------------------------------------------
18// Operator standing rule: no TSV in the estate's data plane. Every other row file here -- .matrix,
19// .refs, .registry, .gates, .plan, .sow -- is PIPE delimited, and this spine was the one place still
20// emitting tabs, so its history was the odd artifact out for no reason anyone had chosen.
21// WSP_FS is what new rows are WRITTEN with. wsp_is_fs accepts EITHER separator on READ, deliberately:
22// the history already holds rows in the old shape and the regression detector reads them to find a
23// prior baseline. A reader that only accepted the new separator would silently lose that baseline and
24// report every run as a first run -- a format change that destroys the time spine it was meant to
25// tidy. Accepting both keeps every existing row readable while nothing new is ever written as TSV.
26// It lives HERE because this spine is the single reader shared by nx_wpt_runner and nx_wpt_reftest;
27// a separator defined in each writer would be two owners of one wire format, which is where drift
28// starts -- the same reason this file was extracted in the first place.
29// ALSO DEFINED ABOVE BOTH READERS ON PURPOSE: nx is single-pass, and wsp_hist_find splits fields too.
30const WSP_FS: i64 = 124
31const WSP_FS_LEGACY: i64 = 9
32func wsp_is_fs(c: i64) -> i64 {
33 if c == WSP_FS { return 1 }
34 if c == WSP_FS_LEGACY { return 1 }
35 return 0
36}
37
38func wsp_scat(dst: *u8, off: i64, src: *u8) -> i64 { var i: i64 = 0; while src[i] != (0 as u8) { dst[off+i] = src[i]; i = i + 1 } return off + i }
39func wsp_scatn(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { dst[off+i] = src[i]; i = i + 1 } return off + n }
40func wsp_ends_with(s: *u8, n: i64, suf: *u8, sn: i64) -> i64 {
41 if n < sn { return 0 }
42 var i: i64 = 0
43 while i < sn { if s[n-sn+i] != suf[i] { return 0 } i = i + 1 }
44 return 1
45}
46func wsp_hex16(dig: *u8, out: *u8) -> i64 {
47 let hx: *u8 = "0123456789abcdef" as *u8
48 var i: i64 = 0
49 while i < 8 {
50 let b: i64 = dig[i] as i64
51 out[i*2] = hx[b / 16]
52 out[i*2+1] = hx[b % 16]
53 i = i + 1
54 }
55 out[16] = 0 as u8
56 return 16
57}
58// sha256(this very binary) as 16 hex chars. 0 ok / 1 cannot read self (caller must REFUSE an unkeyed run).
59func wsp_engine_sha(out: *u8) -> i64 {
60 let lp: *i64 = sys_mmap(16) as *i64
61 let b: *u8 = sys_read_file("/proc/self/exe" as *u8, lp)
62 if (b as i64) == 0 { return 1 }
63 let c: *Sha256 = sys_mmap(512) as *Sha256
64 sha256_init(c)
65 sha256_update(c, b, lp[0])
66 let dig: *u8 = sys_mmap(40)
67 sha256_final(c, dig)
68 wsp_hex16(dig, out)
69 sys_free_file(b, lp[0])
70 return 0
71}
72func wsp_name_lt(a: *u8, b: *u8) -> i64 {
73 var i: i64 = 0
74 var going: i64 = 1
75 var r: i64 = 0
76 while going == 1 {
77 let ca: i64 = a[i] as i64
78 let cb: i64 = b[i] as i64
79 if ca < cb { r = 1; going = 0 }
80 if going == 1 { if ca > cb { r = 0; going = 0 } }
81 if going == 1 { if ca == 0 { r = 0; going = 0 } }
82 i = i + 1
83 }
84 return r
85}
86// every *.<suf> name in dir into namebuf (NUL-joined) + offs[], SORTED byte-wise. -1 = unreadable dir.
87func wsp_collect_names(dir: *u8, suf: *u8, namebuf: *u8, nbcap: i64, offs: *i64, maxn: i64) -> i64 {
88 let fd: i64 = sys_openat_rd(dir)
89 if fd < 0 { return 0 - 1 }
90 var sn: i64 = 0
91 while suf[sn] != (0 as u8) { sn = sn + 1 }
92 let dbuf: *u8 = sys_mmap(1048576)
93 var cnt: i64 = 0
94 var nb: i64 = 0
95 var going: i64 = 1
96 while going == 1 {
97 let nread: i64 = sys_getdents64(fd, dbuf, 1048576)
98 if nread <= 0 { going = 0 } else {
99 var off: i64 = 0
100 while off < nread {
101 let rec: *u8 = ((dbuf as i64) + off) as *u8
102 let rl: i64 = dirent_reclen(rec)
103 let nm: *u8 = dirent_name(rec)
104 var nl: i64 = 0
105 while nm[nl] != (0 as u8) { nl = nl + 1 }
106 if wsp_ends_with(nm, nl, suf, sn) == 1 { if cnt < maxn { if (nb + nl + 1) < nbcap {
107 offs[cnt] = nb
108 nb = wsp_scatn(namebuf, nb, nm, nl)
109 namebuf[nb] = 0 as u8; nb = nb + 1
110 cnt = cnt + 1
111 } } }
112 off = off + rl
113 }
114 }
115 }
116 sys_close(fd)
117 var i: i64 = 1
118 while i < cnt {
119 let key: i64 = offs[i]
120 var j: i64 = i - 1
121 var moving: i64 = 1
122 while moving == 1 {
123 if j < 0 { moving = 0 } else {
124 if wsp_name_lt(((namebuf as i64) + key) as *u8, ((namebuf as i64) + offs[j]) as *u8) == 1 {
125 offs[j+1] = offs[j]
126 j = j - 1
127 } else { moving = 0 }
128 }
129 }
130 offs[j+1] = key
131 i = i + 1
132 }
133 return cnt
134}
135// content hash over the SORTED corpus: name+NUL then bytes per file; READFAIL sentinel keeps a
136// vanished file visible in the key. out = 16 hex chars.
137func wsp_corpus_sha(dir: *u8, namebuf: *u8, offs: *i64, n: i64, out: *u8) -> i64 {
138 let c: *Sha256 = sys_mmap(512) as *Sha256
139 sha256_init(c)
140 let path: *u8 = sys_mmap(4096)
141 var i: i64 = 0
142 while i < n {
143 let nm: *u8 = ((namebuf as i64) + offs[i]) as *u8
144 var pp: i64 = wsp_scat(path, 0, dir)
145 pp = wsp_scat(path, pp, "/" as *u8)
146 pp = wsp_scat(path, pp, nm)
147 path[pp] = 0 as u8
148 var nl: i64 = 0
149 while nm[nl] != (0 as u8) { nl = nl + 1 }
150 sha256_update(c, nm, nl + 1)
151 let lp: *i64 = sys_mmap(16) as *i64
152 let b: *u8 = sys_read_file(path, lp)
153 if (b as i64) == 0 { sha256_update(c, "READFAIL" as *u8, 8) } else {
154 sha256_update(c, b, lp[0])
155 sys_free_file(b, lp[0])
156 }
157 i = i + 1
158 }
159 let dig: *u8 = sys_mmap(40)
160 sha256_final(c, dig)
161 wsp_hex16(dig, out)
162 return 0
163}
164func wsp_field_eq(h: *u8, p: i64, q: i64, v: *u8) -> i64 {
165 var k: i64 = 0
166 while (p + k) < q {
167 if v[k] == (0 as u8) { return 0 }
168 if v[k] != h[p+k] { return 0 }
169 k = k + 1
170 }
171 if v[k] != (0 as u8) { return 0 }
172 return 1
173}
174// newest TSV row whose col1 == c1v and (when non-0) col2 == c2v, col3 == c3v. rowout[0] = row length.
175func wsp_hist_find(h: *u8, hn: i64, c1v: *u8, c2v: *u8, c3v: *u8, rowout: *i64) -> i64 {
176 var best: i64 = 0 - 1
177 var i: i64 = 0
178 while i < hn {
179 var e: i64 = i
180 while e < hn { if h[e] == (10 as u8) { break } e = e + 1 }
181 if e > i {
182 var col: i64 = 0
183 var p: i64 = i
184 var m1: i64 = 0
185 var m2: i64 = 1
186 var m3: i64 = 1
187 if (c2v as i64) != 0 { m2 = 0 }
188 if (c3v as i64) != 0 { m3 = 0 }
189 while p < e {
190 var q: i64 = p
191 while q < e { if wsp_is_fs(h[q] as i64) == 1 { break } q = q + 1 }
192 if col == 1 { m1 = wsp_field_eq(h, p, q, c1v) }
193 if col == 2 { if (c2v as i64) != 0 { m2 = wsp_field_eq(h, p, q, c2v) } }
194 if col == 3 { if (c3v as i64) != 0 { m3 = wsp_field_eq(h, p, q, c3v) } }
195 col = col + 1
196 p = q + 1
197 }
198 if m1 == 1 { if m2 == 1 { if m3 == 1 { best = i; rowout[0] = e - i } } }
199 }
200 i = e + 1
201 }
202 return best
203}
204func wsp_hist_col_int(h: *u8, ro: i64, rl: i64, c: i64) -> i64 {
205 var col: i64 = 0
206 var p: i64 = ro
207 let e: i64 = ro + rl
208 while p < e {
209 var q: i64 = p
210 while q < e { if wsp_is_fs(h[q] as i64) == 1 { break } q = q + 1 }
211 if col == c {
212 var v: i64 = 0
213 var k: i64 = p
214 while k < q { let ch: i64 = h[k] as i64; if ch >= 48 { if ch <= 57 { v = v * 10 + (ch - 48) } } k = k + 1 }
215 return v
216 }
217 col = col + 1
218 p = q + 1
219 }
220 return 0 - 1
221}
222func wsp_base_of(dir: *u8, out: *u8) -> i64 {
223 var n: i64 = 0
224 while dir[n] != (0 as u8) { n = n + 1 }
225 var stripping: i64 = 1
226 while stripping == 1 { if n > 0 { if dir[n-1] == (47 as u8) { n = n - 1 } else { stripping = 0 } } else { stripping = 0 } }
227 var s: i64 = n
228 var seeking: i64 = 1
229 while seeking == 1 { if s > 0 { if dir[s-1] == (47 as u8) { seeking = 0 } else { s = s - 1 } } else { seeking = 0 } }
230 var o: i64 = 0
231 var i: i64 = s
232 while i < n { out[o] = dir[i]; o = o + 1; i = i + 1 }
233 out[o] = 0 as u8
234 return o
235}
236// append row bytes to the history file; 0 ok / 1 open failed (caller must SAY the row was not durably kept)
237func wsp_append(histpath: *u8, row: *u8, ro: i64) -> i64 {
238 let fd: i64 = sys_openat_append(histpath, 0x1a4)
239 if fd < 0 { return 1 }
240 sys_write(fd, row, ro)
241 sys_close(fd)
242 return 0
243}