nx_uxcx_grade.nx source
↩ module page · 156 lines · 5748 B
1// nx_uxcx_grade.nx -- sovereign UX/CX grader core (substrate-native).
2//
3// Byte-scan detectors for the two operator-reported bug classes plus key
4// structural checks. No third-party, no Python: pure *u8 scanning over the
5// artifact bytes (read via sys_read_file by a caller, or embedded fixtures
6// by the gate nx_uxcx_grade_test.nx). The Python nishilib.uxcx grader is the
7// cross-check BENCHMARK; this is the sovereign engine.
8//
9// Every check returns 1 = good/clean, 0 = bad/bug-present.
10//
11// genealogy_id: nishi_library_uxcx_sovereign_2026_06_30
12// lineage_id: uxcx_grader_core
13
14// strlen for a null-terminated literal.
15func uxg_len(s: *u8) -> i64 {
16 var n: i64 = 0
17 while s[n] != 0 { n = n + 1 }
18 return n
19}
20
21// Naive substring search: 1 if needle occurs in haystack, else 0.
22func uxg_has(hay: *u8, hay_n: i64, needle: *u8, n_n: i64) -> i64 {
23 if n_n == 0 { return 1 }
24 if hay_n < n_n { return 0 }
25 let last: i64 = hay_n - n_n
26 var i: i64 = 0
27 while i <= last {
28 var j: i64 = 0
29 var st: i64 = 0
30 while st == 0 {
31 if j >= n_n { st = 2 }
32 if st == 0 {
33 if hay[i + j] != needle[j] { st = 1 }
34 if st == 0 { j = j + 1 }
35 }
36 }
37 if st == 2 { return 1 }
38 i = i + 1
39 }
40 return 0
41}
42
43// Convenience: search for a null-terminated literal.
44func uxg_has_lit(hay: *u8, hay_n: i64, needle: *u8) -> i64 {
45 return uxg_has(hay, hay_n, needle, uxg_len(needle))
46}
47
48// CHECK escaping integrity: clean(1) unless a DOUBLE-encoded entity is
49// present (the literal-"&" bug as the reader would see it).
50func uxg_escaping_ok(html: *u8, n: i64) -> i64 {
51 if uxg_has_lit(html, n, "&amp;" as *u8) == 1 { return 0 }
52 if uxg_has_lit(html, n, "&lt;" as *u8) == 1 { return 0 }
53 if uxg_has_lit(html, n, "&gt;" as *u8) == 1 { return 0 }
54 if uxg_has_lit(html, n, "&quot;" as *u8) == 1 { return 0 }
55 if uxg_has_lit(html, n, "&#" as *u8) == 1 { return 0 }
56 return 1
57}
58
59// CHECK back navigation: ok(1) unless a back control is wired inline straight
60// to history.back() AND there is no in-app fallback (goBack / userNavigated /
61// lastListHash). Distinguishes the buggy inline arrow wiring from the fixed
62// named-handler form.
63func uxg_back_ok(js: *u8, n: i64) -> i64 {
64 if uxg_has_lit(js, n, "=> history.back()" as *u8) == 0 { return 1 }
65 if uxg_has_lit(js, n, "lastListHash" as *u8) == 1 { return 1 }
66 if uxg_has_lit(js, n, "userNavigated" as *u8) == 1 { return 1 }
67 if uxg_has_lit(js, n, "goBack" as *u8) == 1 { return 1 }
68 return 0
69}
70
71// CHECK mobile viewport: width=device-width present.
72func uxg_viewport_ok(html: *u8, n: i64) -> i64 {
73 return uxg_has_lit(html, n, "width=device-width" as *u8)
74}
75
76// CHECK skip-to-content link present.
77func uxg_skiplink_ok(html: *u8, n: i64) -> i64 {
78 return uxg_has_lit(html, n, "skip-link" as *u8)
79}
80
81// Combined permil over the four checks (250 each), given the artifact bytes
82// classified as page(html)+js. Mirrors the Python grader's permil flavour.
83func uxg_permil(html: *u8, hn: i64, js: *u8, jn: i64) -> i64 {
84 var p: i64 = 0
85 if uxg_escaping_ok(html, hn) == 1 { p = p + 250 }
86 if uxg_viewport_ok(html, hn) == 1 { p = p + 250 }
87 if uxg_skiplink_ok(html, hn) == 1 { p = p + 250 }
88 if uxg_back_ok(js, jn) == 1 { p = p + 250 }
89 return p
90}
91
92// ---- additional byte-scannable checks (page) ----
93
94// <html lang="..."> declared.
95func uxg_lang_ok(html: *u8, n: i64) -> i64 {
96 return uxg_has_lit(html, n, "lang=\"" as *u8)
97}
98
99// non-empty <title>.
100func uxg_title_ok(html: *u8, n: i64) -> i64 {
101 return uxg_has_lit(html, n, "<title>" as *u8)
102}
103
104// a <nav> landmark.
105func uxg_nav_ok(html: *u8, n: i64) -> i64 {
106 return uxg_has_lit(html, n, "<nav" as *u8)
107}
108
109// sovereignty: no externally-loaded asset (src="http...). Content <a href>
110// links are not flagged (only src= asset loads).
111func uxg_sovereign_ok(html: *u8, n: i64) -> i64 {
112 if uxg_has_lit(html, n, "src=\"http" as *u8) == 1 { return 0 }
113 return 1
114}
115
116// ---- additional byte-scannable checks (spa js) ----
117
118// async views set a loading placeholder.
119func uxg_loading_ok(js: *u8, n: i64) -> i64 {
120 if uxg_has_lit(js, n, "loading" as *u8) == 1 { return 1 }
121 if uxg_has_lit(js, n, "searching" as *u8) == 1 { return 1 }
122 return 0
123}
124
125// failures are caught AND surfaced.
126func uxg_error_ok(js: *u8, n: i64) -> i64 {
127 if uxg_has_lit(js, n, "catch" as *u8) == 0 { return 0 }
128 if uxg_has_lit(js, n, "empty" as *u8) == 1 { return 1 }
129 if uxg_has_lit(js, n, "failed" as *u8) == 1 { return 1 }
130 if uxg_has_lit(js, n, "error" as *u8) == 1 { return 1 }
131 return 0
132}
133
134// empty result sets are explained.
135func uxg_empty_ok(js: *u8, n: i64) -> i64 {
136 if uxg_has_lit(js, n, "no matches" as *u8) == 1 { return 1 }
137 if uxg_has_lit(js, n, "empty" as *u8) == 1 { return 1 }
138 return 0
139}
140
141// Full grade: permil over all 11 checks (7 page + 4 js), evenly weighted.
142func uxg_grade_full(page: *u8, pn: i64, js: *u8, jn: i64) -> i64 {
143 var pass: i64 = 0
144 if uxg_escaping_ok(page, pn) == 1 { pass = pass + 1 }
145 if uxg_viewport_ok(page, pn) == 1 { pass = pass + 1 }
146 if uxg_skiplink_ok(page, pn) == 1 { pass = pass + 1 }
147 if uxg_lang_ok(page, pn) == 1 { pass = pass + 1 }
148 if uxg_title_ok(page, pn) == 1 { pass = pass + 1 }
149 if uxg_nav_ok(page, pn) == 1 { pass = pass + 1 }
150 if uxg_sovereign_ok(page, pn) == 1 { pass = pass + 1 }
151 if uxg_back_ok(js, jn) == 1 { pass = pass + 1 }
152 if uxg_loading_ok(js, jn) == 1 { pass = pass + 1 }
153 if uxg_error_ok(js, jn) == 1 { pass = pass + 1 }
154 if uxg_empty_ok(js, jn) == 1 { pass = pass + 1 }
155 return (pass * 1000) / 11
156}