code wiki / _hdl_build / nx_outbench.nx
nx_outbench.nx source
↩ module page · 284 lines · 13886 B
1// nx_outbench.nx -- OUTPUT-CORRECTNESS BENCH: does a generated site's OUTPUT hold together?
2//
3// ★WHY THIS EXISTS (the gap this lane measured and filed, 2026-07-25): nothing scored whether generated
4// output is CORRECT. A generation census scores CAPABILITY ("can it generate"); a craft judge scores
5// design MARKERS on ONE finished page. Neither asks the questions that caught every real defect in this
6// lane -- each of which was LIVE and passed every craft/a11y/contrast checker:
7// * a coffee shop's product page selling the GENERATOR'S OWN software subscriptions (leak)
8// * a site shipping TWO visual identities across its pages (skin)
9// * a tool reporting success having written NOTHING (set)
10// * links pointing at pages that were never emitted (link)
11// So a generator can score well on both existing rulers while shipping broken sites.
12//
13// GENERATOR-AGNOSTIC BY CONSTRUCTION: it reads a DIRECTORY of emitted files and needs no knowledge of who
14// produced them, so it runs against the field's output exactly as it runs against ours. Offline -- no
15// network, no live edge, no vantage problem (deliberately complementary to the live link sentinel).
16// usage: nx_outbench bench <dir> [leakterm ...] | nx_outbench selftest
17// Scores four classes in permille and an overall (worst-class-weighted) figure.
18// expect_exit: 0 license_tier: ORIGINAL
19import "nx_syscalls.nx"
20import "nx_emit_guard.nx"
21const OB_MAGIC_1024: i64 = 1024
22
23const OB_MAXFILES: i64 = 256
24const OB_NAMEW: i64 = 128
25const OB_DIRBUF: i64 = 65536
26const OB_FILEBUF: i64 = 1048576
27const OB_PERMILLE: i64 = 1000
28
29func ob_say(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
30func ob_num(v: i64) -> i64 { if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } let t: *u8 = sys_mmap(28); var k: i64 = 0; while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } while k > 0 { k = k - 1; sys_write(1, (((t as i64) + k) as *u8), 1) } return 0 }
31func ob_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
32func ob_slot(base: *u8, i: i64) -> *u8 { return (((base as i64) + i * OB_NAMEW) as *u8) }
33func ob_eq(a: *u8, b: *u8) -> i64 {
34 var i: i64 = 0
35 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
36 if b[i] != (0 as u8) { return 0 }
37 return 1
38}
39// substring search in a length-bounded buffer
40func ob_find(buf: *u8, len: i64, lit: *u8, from: i64) -> i64 {
41 let ll: i64 = ob_len(lit)
42 if ll == 0 { return 0 - 1 }
43 var i: i64 = from
44 while i + ll <= len {
45 var j: i64 = 0
46 var ok: i64 = 1
47 while j < ll { if buf[i + j] != lit[j] { ok = 0; j = ll } else { j = j + 1 } }
48 if ok == 1 { return i }
49 i = i + 1
50 }
51 return 0 - 1
52}
53func ob_ends(name: *u8, suf: *u8) -> i64 {
54 let nl: i64 = ob_len(name)
55 let sl: i64 = ob_len(suf)
56 if sl > nl { return 0 }
57 var i: i64 = 0
58 while i < sl { if name[nl - sl + i] != suf[i] { return 0 } i = i + 1 }
59 return 1
60}
61func ob_join(dst: *u8, dir: *u8, name: *u8) -> i64 {
62 var o: i64 = 0
63 var i: i64 = 0
64 while dir[i] != (0 as u8) { dst[o] = dir[i]; o = o + 1; i = i + 1 }
65 dst[o] = 47 as u8
66 o = o + 1
67 i = 0
68 while name[i] != (0 as u8) { dst[o] = name[i]; o = o + 1; i = i + 1 }
69 dst[o] = 0 as u8
70 return o
71}
72func ob_has_name(names: *u8, n: i64, want: *u8) -> i64 {
73 var i: i64 = 0
74 while i < n { if ob_eq(ob_slot(names, i), want) == 1 { return 1 } i = i + 1 }
75 return 0
76}
77
78// ---- CLASS 1: LINK RESOLUTION. every plain internal .html href must name a file that exists ----
79func ob_links(buf: *u8, len: i64, names: *u8, nfiles: i64, tmp: *u8, tot: *i64) -> i64 {
80 var bad: i64 = 0
81 var at: i64 = ob_find(buf, len, "href=\"" as *u8, 0)
82 while at >= 0 {
83 var m: i64 = at + 6
84 var tl: i64 = 0
85 while m < len {
86 if buf[m] == (34 as u8) { m = len } else {
87 if tl < OB_NAMEW - 8 { tmp[tl] = buf[m]; tl = tl + 1 }
88 m = m + 1
89 }
90 }
91 tmp[tl] = 0 as u8
92 var plain: i64 = 1
93 var q: i64 = 0
94 while q < tl { if tmp[q] == (47 as u8) { plain = 0 } if tmp[q] == (58 as u8) { plain = 0 } if tmp[q] == (35 as u8) { plain = 0 } q = q + 1 }
95 if plain == 1 { if ob_ends(tmp, ".html" as *u8) == 1 {
96 tot[0] = tot[0] + 1
97 if ob_has_name(names, nfiles, tmp) == 0 { bad = bad + 1 }
98 } }
99 at = ob_find(buf, len, "href=\"" as *u8, at + 6)
100 }
101 return bad
102}
103
104// ---- CLASS 3: DESIGN SIGNATURE. the first CSS custom-property prefix a page declares ("--kc-", "--nx-")
105// identifies its design language without knowing anything about the generator. Pages that disagree
106// with the plurality signature are shipping a second visual identity. ----
107func ob_signature(buf: *u8, len: i64, out: *u8) -> i64 {
108 let at: i64 = ob_find(buf, len, "--" as *u8, 0)
109 if at < 0 { out[0] = 0 as u8; return 0 }
110 var o: i64 = 0
111 var i: i64 = at
112 // capture "--word-" up to and including the first dash after the prefix word
113 while i < len {
114 let c: i64 = buf[i] as i64
115 if o >= 24 { i = len } else {
116 if c == 58 { i = len } else {
117 out[o] = buf[i]
118 o = o + 1
119 if o > 3 { if c == 45 { i = len } }
120 i = i + 1
121 }
122 }
123 }
124 out[o] = 0 as u8
125 return o
126}
127
128func main(argc: i64, argv: *i64) -> i64 {
129 if argc < 2 {
130 ob_say("usage: nx_outbench {bench <dir> [leakterm ...] | selftest}\n" as *u8)
131 ob_say(" offline, generator-agnostic correctness bench over a directory of emitted pages\n" as *u8)
132 sys_exit(2)
133 return 2
134 }
135 let verb: *u8 = argv[1] as *u8
136 var dir: *u8 = "web_assets" as *u8
137 var selftest: i64 = 0
138 if ob_eq(verb, "selftest" as *u8) == 1 { selftest = 1; dir = "web_assets/obkat" as *u8 }
139 else { if argc < 3 { ob_say("usage: nx_outbench bench <dir>\n" as *u8); sys_exit(2); return 2 } dir = argv[2] as *u8 }
140
141 // ---- enumerate the directory (generator-agnostic: we are told nothing, we look) ----
142 let names: *u8 = eg_buf(OB_MAXFILES * OB_NAMEW, "outbench name table" as *u8)
143 let dbuf: *u8 = eg_buf(OB_DIRBUF, "outbench dirent buffer" as *u8)
144 let fbuf: *u8 = eg_buf(OB_FILEBUF, "outbench file buffer" as *u8)
145 let tmp: *u8 = eg_buf(OB_NAMEW, "outbench scratch" as *u8)
146 let sig: *u8 = eg_buf(OB_NAMEW, "outbench signature" as *u8)
147 let sigs: *u8 = eg_buf(OB_MAXFILES * OB_NAMEW, "outbench signature table" as *u8)
148 let path: *u8 = eg_buf(OB_MAGIC_1024, "outbench path" as *u8)
149 var nfiles: i64 = 0
150 let fd: i64 = sys_openat_rd(dir)
151 if fd < 0 { ob_say("OUTBENCH-FAIL cannot open dir " as *u8); ob_say(dir); ob_say("\n" as *u8); sys_exit(3); return 3 }
152 var got: i64 = sys_getdents64(fd, dbuf, OB_DIRBUF)
153 while got > 0 {
154 var off: i64 = 0
155 while off < got {
156 let rec: *u8 = (((dbuf as i64) + off) as *u8)
157 let nm: *u8 = dirent_name(rec)
158 if nm[0] != (46 as u8) {
159 if nfiles < OB_MAXFILES {
160 var k: i64 = 0
161 let slot: *u8 = ob_slot(names, nfiles)
162 while nm[k] != (0 as u8) { if k < OB_NAMEW - 1 { slot[k] = nm[k] } k = k + 1 }
163 slot[k] = 0 as u8
164 nfiles = nfiles + 1
165 }
166 }
167 off = off + dirent_reclen(rec)
168 }
169 got = sys_getdents64(fd, dbuf, OB_DIRBUF)
170 }
171 sys_close(fd)
172
173 // ---- walk every .html page: links + signature + leak ----
174 let ltot: *i64 = eg_buf(8, "outbench link counter") as *i64
175 ltot[0] = 0
176 var lbad: i64 = 0
177 var pages: i64 = 0
178 var leakhits: i64 = 0
179 var i: i64 = 0
180 while i < nfiles {
181 let nm: *u8 = ob_slot(names, i)
182 if ob_ends(nm, ".html" as *u8) == 1 {
183 ob_join(path, dir, nm)
184 let flen: *i64 = eg_buf(8, "outbench flen") as *i64
185 let raw: *u8 = sys_read_file(path, flen)
186 if (raw as i64) != 0 {
187 let n: i64 = flen[0]
188 lbad = lbad + ob_links(raw, n, names, nfiles, tmp, ltot)
189 ob_signature(raw, n, sig)
190 var k: i64 = 0
191 let ss: *u8 = ob_slot(sigs, pages)
192 while sig[k] != (0 as u8) { ss[k] = sig[k]; k = k + 1 }
193 ss[k] = 0 as u8
194 // leak terms: universal placeholder tells, plus any the caller adds
195 if ob_find(raw, n, "Lorem ipsum" as *u8, 0) >= 0 { leakhits = leakhits + 1 }
196 if ob_find(raw, n, "lorem ipsum" as *u8, 0) >= 0 { leakhits = leakhits + 1 }
197 if ob_find(raw, n, "Your Company" as *u8, 0) >= 0 { leakhits = leakhits + 1 }
198 if ob_find(raw, n, "Placeholder" as *u8, 0) >= 0 { leakhits = leakhits + 1 }
199 if ob_find(raw, n, "TODO" as *u8, 0) >= 0 { leakhits = leakhits + 1 }
200 // NOTE: "example.com" is deliberately NOT a default leak term. RFC 2606 reserves it for
201 // exactly this legitimate use, and a correct contact form carries jane@example.com as a
202 // field PLACEHOLDER. The bench's first real run flagged our own correct forms on it --
203 // a false positive in this rule, found by pointing the ruler at its author. Callers whose
204 // context warrants it can still pass it as an explicit term.
205 var ai: i64 = 3
206 while ai < argc {
207 if ob_find(raw, n, argv[ai] as *u8, 0) >= 0 { leakhits = leakhits + 1 }
208 ai = ai + 1
209 }
210 pages = pages + 1
211 }
212 }
213 i = i + 1
214 }
215
216 // ---- class scores ----
217 // ★ NOT-APPLICABLE, never a vacuous pass. Benching the FIELD exposed this: their pages use absolute
218 // and clean URLs, so ZERO plain .html hrefs were found and the class reported a PERFECT 1000 while
219 // checking NOTHING. A ruler that scores full marks on an unexamined property is lying by omission
220 // (the estate law: unproven = ABSENT). -1 means the class could not be assessed on this input.
221 var link_pm: i64 = 0 - 1
222 if ltot[0] > 0 { link_pm = ((ltot[0] - lbad) * OB_PERMILLE) / ltot[0] }
223 // set completeness: pages exist, sitemap present, robots present
224 var setparts: i64 = 0
225 if pages > 0 { setparts = setparts + 1 }
226 if ob_has_name(names, nfiles, "sitemap.xml" as *u8) == 1 { setparts = setparts + 1 }
227 if ob_has_name(names, nfiles, "robots.txt" as *u8) == 1 { setparts = setparts + 1 }
228 let set_pm: i64 = (setparts * OB_PERMILLE) / 3
229 // design signature agreement vs the plurality
230 var best: i64 = 0
231 var bi: i64 = 0
232 while bi < pages {
233 var c: i64 = 0
234 var bj: i64 = 0
235 while bj < pages { if ob_eq(ob_slot(sigs, bi), ob_slot(sigs, bj)) == 1 { c = c + 1 } bj = bj + 1 }
236 if c > best { best = c }
237 bi = bi + 1
238 }
239 var skin_pm: i64 = OB_PERMILLE
240 if pages > 0 { skin_pm = (best * OB_PERMILLE) / pages }
241 var leak_pm: i64 = OB_PERMILLE
242 if pages > 0 { if leakhits > 0 { leak_pm = 0 } }
243 // overall = the WORST class (a site is only as correct as its weakest guarantee)
244 // overall = worst of the APPLICABLE classes only; a class that could not be assessed neither
245 // helps nor hurts, and the count of applicable classes is reported so the figure can be judged.
246 var overall: i64 = OB_PERMILLE
247 var applicable: i64 = 0
248 if link_pm >= 0 { applicable = applicable + 1; if link_pm < overall { overall = link_pm } }
249 if set_pm >= 0 { applicable = applicable + 1; if set_pm < overall { overall = set_pm } }
250 if skin_pm >= 0 { applicable = applicable + 1; if skin_pm < overall { overall = skin_pm } }
251 if leak_pm >= 0 { applicable = applicable + 1; if leak_pm < overall { overall = leak_pm } }
252 if applicable == 0 { overall = 0 - 1 }
253
254 ob_say("{\"organ\":\"nx_outbench\",\"v\":1,\"dir\":\"" as *u8); ob_say(dir)
255 ob_say("\",\"files\":" as *u8); ob_num(nfiles)
256 ob_say(",\"pages\":" as *u8); ob_num(pages)
257 ob_say(",\"link_permille\":" as *u8); ob_num(link_pm)
258 ob_say(",\"links_checked\":" as *u8); ob_num(ltot[0])
259 ob_say(",\"links_unresolved\":" as *u8); ob_num(lbad)
260 ob_say(",\"set_permille\":" as *u8); ob_num(set_pm)
261 ob_say(",\"skin_permille\":" as *u8); ob_num(skin_pm)
262 ob_say(",\"leak_permille\":" as *u8); ob_num(leak_pm)
263 ob_say(",\"leak_hits\":" as *u8); ob_num(leakhits)
264 ob_say(",\"overall_permille\":" as *u8); ob_num(overall)
265 ob_say(",\"applicable_classes\":" as *u8); ob_num(applicable)
266 ob_say(",\"env\":\"offline; ONE SITE PER DIRECTORY assumed (a folder of unrelated pages makes skin meaningless); link class requires RELATIVE .html hrefs and reports -1 NOT-APPLICABLE for absolute/clean-URL conventions rather than a vacuous pass; set assumes a fully emitted site, not a capture; overall = worst APPLICABLE class\"}\n" as *u8)
267
268 if selftest == 1 {
269 // the fixture is authored to be BROKEN in every class; a bench that scores it well is useless
270 var fails: i64 = 0
271 if lbad < 1 { fails = fails + 1 }
272 if link_pm < 0 { fails = fails + 1 }
273 if leakhits < 1 { fails = fails + 1 }
274 if skin_pm >= OB_PERMILLE { fails = fails + 1 }
275 if set_pm >= OB_PERMILLE { fails = fails + 1 }
276 ob_say("NX-OUTBENCH selftest fails=" as *u8); ob_num(fails)
277 if fails == 0 { ob_say(" verdict=GREEN (a deliberately broken fixture scored badly in ALL FOUR classes)\n" as *u8); sys_exit(0); return 0 }
278 ob_say(" verdict=RED (the bench failed to detect a defect it was shown)\n" as *u8)
279 sys_exit(1)
280 return 1
281 }
282 sys_exit(0)
283 return 0
284}