code wiki / _hdl_build / nx_chartlay_adoption_gate.nx
nx_chartlay_adoption_gate.nx source
↩ module page · 505 lines · 27220 B
1// nx_chartlay_adoption_gate.nx -- datavis DV9 (2026-09-15): THE FLEET RATCHET for the one chart-layout ruler.
2// Names every SVG-emitting function in the compare generators (the sources are DATA in cl_adoption.conf) and refuses
3// a chart that scales, places or colours by hand. An emitter is an ADOPTER when it, or its prefix family (the helpers
4// sharing its name up to the first underscore -- the estate's one-figure-one-prefix convention), calls a ruler verb
5// (the verbs are DATA too); HAND when none does; HAND-COLOUR when the family paints an SVG fill or stroke with a
6// literal rgb() instead of a token. A decorative icon (aria-hidden on the tag) is counted, never judged; an SVG named
7// only in a comment is nothing. Named allow rows exempt one function WITH a reason and can only shrink: an allow row
8// whose function is no longer an offender is STALE and reads RED until deleted, so the ratchet tightens by
9// construction. Planted fixtures prove every class both ways before the live census runs, and the census prints one
10// line per emitter -- a count without a worklist is not actionable. The imprecision lived with: attribute literals
11// are matched in their single-quoted spelling only (the family's emitters use no other), and nx_lineconf_lib is not
12// composed because it is a first-key reader while this file is a ROW LIST with repeated keys. license_tier: ORIGINAL
13import "nx_syscalls.nx"
14import "nx_gate_verdict.nx"
15
16const CA_CONF: *u8 = "knowledge/compare/cl_adoption.conf"
17const CA_CONF_BR: *u8 = "buildroot/knowledge/compare/cl_adoption.conf"
18const CA_BR_PFX: *u8 = "buildroot/"
19const CA_DIR: *u8 = "/tmp/nx_chartlay_adoption_gate/"
20const CA_FX_CONF: *u8 = "/tmp/nx_chartlay_adoption_gate/fixture.conf"
21const CA_FX_CONF_ALLOW: *u8 = "/tmp/nx_chartlay_adoption_gate/fixture_allow.conf"
22const CA_FX_CONF_STALE: *u8 = "/tmp/nx_chartlay_adoption_gate/fixture_stale.conf"
23const CA_FX_A: *u8 = "/tmp/nx_chartlay_adoption_gate/adopter.nx"
24const CA_FX_H: *u8 = "/tmp/nx_chartlay_adoption_gate/hand.nx"
25const CA_FX_C: *u8 = "/tmp/nx_chartlay_adoption_gate/colour.nx"
26const CA_FX_I: *u8 = "/tmp/nx_chartlay_adoption_gate/icon.nx"
27const CA_DIR_MODE: i64 = 493
28const CA_FILE_MODE: i64 = 420
29const CA_MAX_FUNCS: i64 = 4096
30const CA_MAX_ROWS: i64 = 256
31const CA_NAME_W: i64 = 96
32const CA_PATH_W: i64 = 256
33const CA_REASON_W: i64 = 320
34const CA_MAX_EMIT: i64 = 512
35const CA_TAG_SPAN: i64 = 400
36const CA_NUM_W: i64 = 24
37const CA_I64: i64 = 8
38const CA_CH_NL: i64 = 10
39const CA_CH_QUOTE: i64 = 34
40const CA_CH_SLASH: i64 = 47
41const CA_CH_BSLASH: i64 = 92
42const CA_CH_PIPE: i64 = 124
43const CA_CH_USCORE: i64 = 95
44const CA_CH_LPAREN: i64 = 40
45const CA_CH_GT: i64 = 62
46const CA_CH_D0: i64 = 48
47const CA_CH_MINUS: i64 = 45
48// per-emitter verdicts
49const CA_NONE: i64 = -1
50const CA_ADOPTER: i64 = 1
51const CA_HAND: i64 = 2
52const CA_HAND_COLOUR: i64 = 3
53// census slots (the partition emitters = adopters + hand + hand_colour is asserted, never assumed)
54const CA_S_SOURCES: i64 = 0
55const CA_S_UNREADABLE: i64 = 1
56const CA_S_FUNCS: i64 = 2
57const CA_S_EMITTERS: i64 = 3
58const CA_S_ADOPTERS: i64 = 4
59const CA_S_HAND: i64 = 5
60const CA_S_HAND_COLOUR: i64 = 6
61const CA_S_ICONS: i64 = 7
62const CA_S_ALLOWED: i64 = 8
63const CA_S_STALE_ALLOW: i64 = 9
64const CA_S_OFFENDERS: i64 = 10
65const CA_S_RULERS: i64 = 11
66const CA_S_EMIT_TRUNC: i64 = 12
67const CA_S_W: i64 = 13
68
69func ca_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
70func ca_put(fd: i64, s: *u8) -> i64 { sys_write(fd, s, ca_len(s)); return 0 }
71func ca_putn(fd: i64, v: i64) -> i64 {
72 let b: *u8 = sys_mmap(CA_NUM_W)
73 var x: i64 = v
74 var neg: i64 = 0
75 if x < 0 { neg = 1; x = 0 - x }
76 var i: i64 = CA_NUM_W
77 if x == 0 { i = i - 1; b[i] = CA_CH_D0 as u8 }
78 while x > 0 { i = i - 1; b[i] = (CA_CH_D0 + (x - (x / 10) * 10)) as u8; x = x / 10 }
79 if neg == 1 { i = i - 1; b[i] = CA_CH_MINUS as u8 }
80 sys_write(fd, b + i, CA_NUM_W - i)
81 return 0
82}
83func ca_q(fd: i64) -> i64 { let b: *u8 = sys_mmap(2); b[0] = CA_CH_QUOTE as u8; sys_write(fd, b, 1); return 0 }
84// first offset of needle in buf[0,n), or -1
85func ca_find(buf: *u8, n: i64, needle: *u8) -> i64 {
86 let m: i64 = ca_len(needle)
87 if m == 0 { return -1 }
88 var i: i64 = 0
89 while i + m <= n {
90 var k: i64 = 0
91 while k < m { if buf[i + k] != needle[k] { break } k = k + 1 }
92 if k == m { return i }
93 i = i + 1
94 }
95 return -1
96}
97func ca_starts(buf: *u8, n: i64, needle: *u8) -> i64 {
98 let m: i64 = ca_len(needle)
99 if m > n { return 0 }
100 var k: i64 = 0
101 while k < m { if buf[k] != needle[k] { return 0 } k = k + 1 }
102 return 1
103}
104func ca_eq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] == b[i] { if a[i] == (0 as u8) { return 1 } i = i + 1 } return 0 }
105// copy [s, s+n) into dst as a nul-terminated string, bounded by cap
106func ca_copy(dst: *u8, cap: i64, s: *u8, n: i64) -> i64 {
107 var m: i64 = n
108 if m > cap - 1 { m = cap - 1 }
109 var i: i64 = 0
110 while i < m { dst[i] = s[i]; i = i + 1 }
111 dst[m] = 0 as u8
112 return m
113}
114func ca_cat(dst: *u8, cap: i64, a: *u8, b: *u8) -> i64 {
115 let la: i64 = ca_len(a)
116 let lb: i64 = ca_len(b)
117 var i: i64 = 0
118 while i < la { if i < cap - 1 { dst[i] = a[i] } i = i + 1 }
119 var j: i64 = 0
120 while j < lb { if la + j < cap - 1 { dst[la + j] = b[j] } j = j + 1 }
121 var e: i64 = la + lb
122 if e > cap - 1 { e = cap - 1 }
123 dst[e] = 0 as u8
124 return e
125}
126func ca_write(path: *u8, s: *u8) -> i64 { sys_unlinkat(path); let fd: i64 = sys_openat_wr(path, CA_FILE_MODE); if fd < 0 { return -1 } sys_write(fd, s, ca_len(s)); sys_close(fd); return 0 }
127func ca_exists(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 }
128// STRIP COMMENTS OUTSIDE STRING LITERALS. A // inside a literal (a URL before an svg tag on the same line) is
129// content, so quote state is tracked and a backslash escape is honoured; the newline that ends a comment is kept so
130// line anchoring survives. Returns the stripped length.
131func ca_strip(src: *u8, n: i64, dst: *u8) -> i64 {
132 var i: i64 = 0
133 var o: i64 = 0
134 var in_str: i64 = 0
135 while i < n {
136 let c: i64 = src[i] as i64
137 if in_str == 1 {
138 dst[o] = src[i]; o = o + 1
139 if c == CA_CH_BSLASH { if i + 1 < n { dst[o] = src[i + 1]; o = o + 1; i = i + 1 } }
140 else { if c == CA_CH_QUOTE { in_str = 0 } }
141 i = i + 1
142 } else {
143 if c == CA_CH_QUOTE { in_str = 1; dst[o] = src[i]; o = o + 1; i = i + 1 }
144 else {
145 var skip: i64 = 0
146 if c == CA_CH_SLASH { if i + 1 < n { if src[i + 1] == (CA_CH_SLASH as u8) { skip = 1 } } }
147 if skip == 1 { while i < n { if src[i] == (CA_CH_NL as u8) { break } i = i + 1 } }
148 else { dst[o] = src[i]; o = o + 1; i = i + 1 }
149 }
150 }
151 }
152 return o
153}
154// the k-th row (0-based) whose line starts with key followed by a pipe: returns the line start, or -1
155func ca_row(buf: *u8, n: i64, key: *u8, k: i64) -> i64 {
156 let kl: i64 = ca_len(key)
157 var seen: i64 = 0
158 var i: i64 = 0
159 while i < n {
160 var le: i64 = i
161 while le < n { if buf[le] == (CA_CH_NL as u8) { break } le = le + 1 }
162 if le - i > kl {
163 if ca_starts(buf + i, le - i, key) == 1 {
164 if buf[i + kl] == (CA_CH_PIPE as u8) {
165 if seen == k { return i }
166 seen = seen + 1
167 }
168 }
169 }
170 i = le + 1
171 }
172 return -1
173}
174// the idx-th pipe field of the line starting at ls, copied nul-terminated into out; returns its length or -1
175func ca_field(buf: *u8, n: i64, ls: i64, idx: i64, out: *u8, cap: i64) -> i64 {
176 var le: i64 = ls
177 while le < n { if buf[le] == (CA_CH_NL as u8) { break } le = le + 1 }
178 var f: i64 = 0
179 var s: i64 = ls
180 var i: i64 = ls
181 while i <= le {
182 var end: i64 = 0
183 if i == le { end = 1 } else { if buf[i] == (CA_CH_PIPE as u8) { end = 1 } }
184 if end == 1 {
185 if f == idx { return ca_copy(out, cap, buf + s, i - s) }
186 f = f + 1
187 s = i + 1
188 }
189 i = i + 1
190 }
191 return -1
192}
193func ca_read_source(path: *u8, out_n: *i64, scratch: *u8) -> *u8 {
194 if ca_exists(path) == 1 { return sys_read_file(path, out_n) }
195 ca_cat(scratch, CA_PATH_W, CA_BR_PFX, path)
196 if ca_exists(scratch) == 1 { return sys_read_file(scratch, out_n) }
197 out_n[0] = -1
198 return 0 as *u8
199}
200// does [p, p+span) of an svg start tag carry aria-hidden before its closing bracket? 1 icon, 0 chart
201func ca_tag_is_icon(buf: *u8, n: i64, p: i64) -> i64 {
202 var e: i64 = p
203 var lim: i64 = p + CA_TAG_SPAN
204 if lim > n { lim = n }
205 while e < lim { if buf[e] == (CA_CH_GT as u8) { break } e = e + 1 }
206 if ca_find(buf + p, e - p, "aria-hidden" as *u8) >= 0 { return 1 }
207 return 0
208}
209// the verdict recorded for a function name in the emitter table, or CA_NONE
210func ca_verdict_of(em_names: *u8, em_verd: *i64, em_n: i64, name: *u8) -> i64 {
211 var i: i64 = 0
212 while i < em_n { if ca_eq(em_names + i * CA_NAME_W, name) == 1 { return em_verd[i] } i = i + 1 }
213 return CA_NONE
214}
215func ca_verdict_word(v: i64) -> *u8 {
216 if v == CA_ADOPTER { return "ADOPTER" as *u8 }
217 if v == CA_HAND { return "HAND" as *u8 }
218 if v == CA_HAND_COLOUR { return "HAND-COLOUR" as *u8 }
219 return "NONE" as *u8
220}
221
222// THE CENSUS. Reads the conf, walks every source row, classifies every function that writes an svg start tag, applies
223// the allow rows, fills st[] and the emitter table. Returns 0, or -1 when the conf itself cannot be read.
224func cl_adoption_gate(conf: *u8, st: *i64, em_names: *u8, em_verd: *i64) -> i64 {
225 var s: i64 = 0
226 while s < CA_S_W { st[s] = 0; s = s + 1 }
227 let cn_p: *i64 = sys_mmap(CA_I64) as *i64
228 let cbuf: *u8 = sys_read_file(conf, cn_p)
229 let cn: i64 = cn_p[0]
230 if cn <= 0 { return -1 }
231 // ruler verbs, each stored with its call paren so "cl_scale" cannot match "cl_scale_dom" by prefix alone
232 let rv: *u8 = sys_mmap(CA_MAX_ROWS * CA_NAME_W)
233 let fld: *u8 = sys_mmap(CA_REASON_W)
234 var nr: i64 = 0
235 var ls: i64 = ca_row(cbuf, cn, "ruler" as *u8, nr)
236 while ls >= 0 {
237 if nr < CA_MAX_ROWS {
238 ca_field(cbuf, cn, ls, 1, fld, CA_NAME_W)
239 let vl: i64 = ca_copy(rv + nr * CA_NAME_W, CA_NAME_W - 1, fld, ca_len(fld))
240 let cell: *u8 = rv + nr * CA_NAME_W
241 cell[vl] = CA_CH_LPAREN as u8
242 cell[vl + 1] = 0 as u8
243 nr = nr + 1
244 }
245 ls = ca_row(cbuf, cn, "ruler" as *u8, nr)
246 }
247 st[CA_S_RULERS] = nr
248 // allow rows: path, function, reason, used flag
249 let ap: *u8 = sys_mmap(CA_MAX_ROWS * CA_PATH_W)
250 let af: *u8 = sys_mmap(CA_MAX_ROWS * CA_NAME_W)
251 let ar: *u8 = sys_mmap(CA_MAX_ROWS * CA_REASON_W)
252 let au: *i64 = sys_mmap(CA_MAX_ROWS * CA_I64) as *i64
253 var na: i64 = 0
254 ls = ca_row(cbuf, cn, "allow" as *u8, na)
255 while ls >= 0 {
256 if na < CA_MAX_ROWS {
257 ca_field(cbuf, cn, ls, 1, ap + na * CA_PATH_W, CA_PATH_W)
258 ca_field(cbuf, cn, ls, 2, af + na * CA_NAME_W, CA_NAME_W)
259 ca_field(cbuf, cn, ls, 3, ar + na * CA_REASON_W, CA_REASON_W)
260 au[na] = 0
261 na = na + 1
262 }
263 ls = ca_row(cbuf, cn, "allow" as *u8, na)
264 }
265 // per-file function table
266 let fn_name: *u8 = sys_mmap(CA_MAX_FUNCS * CA_NAME_W)
267 let fn_s: *i64 = sys_mmap(CA_MAX_FUNCS * CA_I64) as *i64
268 let fn_e: *i64 = sys_mmap(CA_MAX_FUNCS * CA_I64) as *i64
269 let path: *u8 = sys_mmap(CA_PATH_W)
270 let scratch: *u8 = sys_mmap(CA_PATH_W)
271 let sn_p: *i64 = sys_mmap(CA_I64) as *i64
272 var em_n: i64 = 0
273 var si: i64 = 0
274 ls = ca_row(cbuf, cn, "source" as *u8, si)
275 while ls >= 0 {
276 ca_field(cbuf, cn, ls, 1, path, CA_PATH_W)
277 st[CA_S_SOURCES] = st[CA_S_SOURCES] + 1
278 let raw: *u8 = ca_read_source(path, sn_p, scratch)
279 let sn: i64 = sn_p[0]
280 if sn < 0 {
281 st[CA_S_UNREADABLE] = st[CA_S_UNREADABLE] + 1
282 ca_put(1, "SOURCE-UNREADABLE path=" as *u8); ca_put(1, path); ca_put(1, " (a census over an absent file is a green by absence -- fix the row or the tree)\n" as *u8)
283 } else {
284 let dbuf: *u8 = sys_mmap(sn + 1)
285 let dn: i64 = ca_strip(raw, sn, dbuf)
286 // function table: a line starting with "func " opens a function that runs to the next such line
287 var nf: i64 = 0
288 var i: i64 = 0
289 while i < dn {
290 var le: i64 = i
291 while le < dn { if dbuf[le] == (CA_CH_NL as u8) { break } le = le + 1 }
292 if ca_starts(dbuf + i, le - i, "func " as *u8) == 1 {
293 if nf < CA_MAX_FUNCS {
294 var ne: i64 = i + 5
295 while ne < le { if dbuf[ne] == (CA_CH_LPAREN as u8) { break } ne = ne + 1 }
296 ca_copy(fn_name + nf * CA_NAME_W, CA_NAME_W, dbuf + i + 5, ne - (i + 5))
297 fn_s[nf] = i
298 if nf > 0 { fn_e[nf - 1] = i }
299 nf = nf + 1
300 }
301 }
302 i = le + 1
303 }
304 if nf > 0 { fn_e[nf - 1] = dn }
305 st[CA_S_FUNCS] = st[CA_S_FUNCS] + nf
306 // classify every function that writes an svg start tag
307 var f: i64 = 0
308 while f < nf {
309 let body: *u8 = dbuf + fn_s[f]
310 let bl: i64 = fn_e[f] - fn_s[f]
311 var charts: i64 = 0
312 var icons: i64 = 0
313 var p: i64 = ca_find(body, bl, "<svg" as *u8)
314 var off: i64 = 0
315 while p >= 0 {
316 if ca_tag_is_icon(body, bl, off + p) == 1 { icons = icons + 1 } else { charts = charts + 1 }
317 off = off + p + 4
318 p = ca_find(body + off, bl - off, "<svg" as *u8)
319 }
320 let name: *u8 = fn_name + f * CA_NAME_W
321 if charts == 0 {
322 if icons > 0 {
323 st[CA_S_ICONS] = st[CA_S_ICONS] + 1
324 ca_put(1, "ICON file=" as *u8); ca_put(1, path); ca_put(1, " func=" as *u8); ca_put(1, name); ca_put(1, " (aria-hidden: decorative, not judged)\n" as *u8)
325 }
326 } else {
327 st[CA_S_EMITTERS] = st[CA_S_EMITTERS] + 1
328 // the prefix family: every function in this file whose name starts with the emitter's prefix
329 var pl: i64 = 0
330 while name[pl] != (0 as u8) { if name[pl] == (CA_CH_USCORE as u8) { pl = pl + 1; break } pl = pl + 1 }
331 var fam: i64 = 0
332 var ruler_hits: i64 = 0
333 var colour_hits: i64 = 0
334 var g: i64 = 0
335 while g < nf {
336 let gname: *u8 = fn_name + g * CA_NAME_W
337 var same: i64 = 1
338 var q: i64 = 0
339 while q < pl { if gname[q] != name[q] { same = 0; break } q = q + 1 }
340 if same == 1 {
341 fam = fam + 1
342 let gb: *u8 = dbuf + fn_s[g]
343 let gl: i64 = fn_e[g] - fn_s[g]
344 var r: i64 = 0
345 while r < nr { if ca_find(gb, gl, rv + r * CA_NAME_W) >= 0 { ruler_hits = ruler_hits + 1 } r = r + 1 }
346 if ca_find(gb, gl, "fill='rgb(" as *u8) >= 0 { colour_hits = colour_hits + 1 }
347 if ca_find(gb, gl, "stroke='rgb(" as *u8) >= 0 { colour_hits = colour_hits + 1 }
348 }
349 g = g + 1
350 }
351 var v: i64 = CA_HAND
352 if ruler_hits > 0 { v = CA_ADOPTER; if colour_hits > 0 { v = CA_HAND_COLOUR } }
353 if v == CA_ADOPTER { st[CA_S_ADOPTERS] = st[CA_S_ADOPTERS] + 1 }
354 if v == CA_HAND { st[CA_S_HAND] = st[CA_S_HAND] + 1 }
355 if v == CA_HAND_COLOUR { st[CA_S_HAND_COLOUR] = st[CA_S_HAND_COLOUR] + 1 }
356 var allowed: i64 = -1
357 if v != CA_ADOPTER {
358 var a: i64 = 0
359 while a < na {
360 if ca_eq(ap + a * CA_PATH_W, path) == 1 { if ca_eq(af + a * CA_NAME_W, name) == 1 { allowed = a; au[a] = 1; break } }
361 a = a + 1
362 }
363 if allowed >= 0 { st[CA_S_ALLOWED] = st[CA_S_ALLOWED] + 1 } else { st[CA_S_OFFENDERS] = st[CA_S_OFFENDERS] + 1 }
364 }
365 ca_put(1, "EMITTER file=" as *u8); ca_put(1, path); ca_put(1, " func=" as *u8); ca_put(1, name)
366 ca_put(1, " verdict=" as *u8); ca_put(1, ca_verdict_word(v))
367 ca_put(1, " family=" as *u8); ca_putn(1, fam); ca_put(1, " ruler_calls=" as *u8); ca_putn(1, ruler_hits); ca_put(1, " colour_literals=" as *u8); ca_putn(1, colour_hits)
368 if allowed >= 0 { ca_put(1, " ALLOWED: " as *u8); ca_put(1, ar + allowed * CA_REASON_W) }
369 if v != CA_ADOPTER { if allowed < 0 { ca_put(1, " OFFENDER: compose the ruler from the emitter or its prefix family, or add an allow row with the reason" as *u8) } }
370 ca_put(1, "\n" as *u8)
371 if em_n < CA_MAX_EMIT { ca_copy(em_names + em_n * CA_NAME_W, CA_NAME_W, name, ca_len(name)); em_verd[em_n] = v; em_n = em_n + 1 }
372 else { st[CA_S_EMIT_TRUNC] = st[CA_S_EMIT_TRUNC] + 1 }
373 }
374 f = f + 1
375 }
376 }
377 si = si + 1
378 ls = ca_row(cbuf, cn, "source" as *u8, si)
379 }
380 // stale allow rows: an exemption nobody needs any more is a ratchet that stopped tightening
381 var a2: i64 = 0
382 while a2 < na {
383 if au[a2] == 0 {
384 st[CA_S_STALE_ALLOW] = st[CA_S_STALE_ALLOW] + 1
385 ca_put(1, "STALE-ALLOW file=" as *u8); ca_put(1, ap + a2 * CA_PATH_W); ca_put(1, " func=" as *u8); ca_put(1, af + a2 * CA_NAME_W); ca_put(1, " -- no longer an offender (or no longer exists): delete the row so the ratchet tightens\n" as *u8)
386 }
387 a2 = a2 + 1
388 }
389 em_verd[CA_MAX_EMIT] = em_n
390 return 0
391}
392
393// ---- fixtures, assembled at runtime so no source scanner can mistake them for the fleet ----
394func ca_fx_adopter() -> i64 {
395 sys_unlinkat(CA_FX_A)
396 let fd: i64 = sys_openat_wr(CA_FX_A, CA_FILE_MODE)
397 if fd < 0 { return -1 }
398 ca_put(fd, "// adopter fixture: the chart family calls the ruler\nfunc ok_x(v: i64) -> i64 { return cl_scale(v, 0, 100, 10, 700) }\nfunc ok_chart(fd: i64) -> i64 {\n w(fd, " as *u8); ca_q(fd)
399 ca_put(fd, "<p>see https://example.org/</p><svg viewBox='0 0 800 500' width='800' height='500'>" as *u8); ca_q(fd)
400 ca_put(fd, " as *u8)\n w(fd, " as *u8); ca_q(fd); ca_put(fd, "<circle cx='" as *u8); ca_q(fd); ca_put(fd, " as *u8); wn(fd, ok_x(42)); w(fd, " as *u8); ca_q(fd)
401 ca_put(fd, "' fill='var(--nx-color-accent)'/></svg>" as *u8); ca_q(fd); ca_put(fd, " as *u8)\n return 0\n}\n" as *u8)
402 sys_close(fd)
403 return 0
404}
405func ca_fx_hand() -> i64 {
406 sys_unlinkat(CA_FX_H)
407 let fd: i64 = sys_openat_wr(CA_FX_H, CA_FILE_MODE)
408 if fd < 0 { return -1 }
409 ca_put(fd, "func note_only(fd: i64) -> i64 {\n // a legacy <svg chart named in a comment only\n return 0\n}\nfunc hand_chart(fd: i64) -> i64 {\n w(fd, " as *u8); ca_q(fd)
410 ca_put(fd, "<svg viewBox='0 0 800 500'>" as *u8); ca_q(fd); ca_put(fd, " as *u8)\n let x: i64 = 70 + (42 * 660) / 100\n w(fd, " as *u8); ca_q(fd)
411 ca_put(fd, "<circle cx='" as *u8); ca_q(fd); ca_put(fd, " as *u8); wn(fd, x); w(fd, " as *u8); ca_q(fd)
412 ca_put(fd, "' fill='var(--nx-color-accent)'/></svg>" as *u8); ca_q(fd); ca_put(fd, " as *u8)\n return 0\n}\n" as *u8)
413 sys_close(fd)
414 return 0
415}
416func ca_fx_colour() -> i64 {
417 sys_unlinkat(CA_FX_C)
418 let fd: i64 = sys_openat_wr(CA_FX_C, CA_FILE_MODE)
419 if fd < 0 { return -1 }
420 ca_put(fd, "func col_x(v: i64) -> i64 { return cl_scale(v, 0, 100, 10, 700) }\nfunc col_chart(fd: i64) -> i64 {\n w(fd, " as *u8); ca_q(fd)
421 ca_put(fd, "<svg viewBox='0 0 800 500'><rect x='10' y='10' width='40' height='40' fill='rgb(88,64,180)'/></svg>" as *u8); ca_q(fd)
422 ca_put(fd, " as *u8)\n return 0\n}\n" as *u8)
423 sys_close(fd)
424 return 0
425}
426func ca_fx_icon() -> i64 {
427 sys_unlinkat(CA_FX_I)
428 let fd: i64 = sys_openat_wr(CA_FX_I, CA_FILE_MODE)
429 if fd < 0 { return -1 }
430 ca_put(fd, "func icon_nav(fd: i64) -> i64 {\n w(fd, " as *u8); ca_q(fd)
431 ca_put(fd, "<nav><svg viewBox='0 0 24 24' width='22' height='22' aria-hidden='true'><path d='M3 3h18'/></svg></nav>" as *u8); ca_q(fd)
432 ca_put(fd, " as *u8)\n return 0\n}\n" as *u8)
433 sys_close(fd)
434 return 0
435}
436func ca_fx_conf(path: *u8, allow_row: *u8) -> i64 {
437 sys_unlinkat(path)
438 let fd: i64 = sys_openat_wr(path, CA_FILE_MODE)
439 if fd < 0 { return -1 }
440 ca_put(fd, "source|/tmp/nx_chartlay_adoption_gate/adopter.nx\nsource|/tmp/nx_chartlay_adoption_gate/hand.nx\nsource|/tmp/nx_chartlay_adoption_gate/colour.nx\nsource|/tmp/nx_chartlay_adoption_gate/icon.nx\nruler|cl_scale\nruler|cl_place\n" as *u8)
441 ca_put(fd, allow_row)
442 sys_close(fd)
443 return 0
444}
445
446func main() -> i64 {
447 let ctr: *i64 = gv_ctr()
448 sys_mkdir(CA_DIR, CA_DIR_MODE)
449 let st: *i64 = sys_mmap(CA_S_W * CA_I64) as *i64
450 let em_names: *u8 = sys_mmap(CA_MAX_EMIT * CA_NAME_W)
451 let em_verd: *i64 = sys_mmap((CA_MAX_EMIT + 1) * CA_I64) as *i64
452 // ---- planted fixtures: every class both ways ----
453 gv_check_eq("F0-fixtures-written" as *u8, ca_fx_adopter() + ca_fx_hand() + ca_fx_colour() + ca_fx_icon() + ca_fx_conf(CA_FX_CONF, "" as *u8), 0, ctr)
454 gv_check_eq("F1-fixture-census-runs" as *u8, cl_adoption_gate(CA_FX_CONF, st, em_names, em_verd), 0, ctr)
455 gv_check_eq("F2-four-sources-all-readable" as *u8, st[CA_S_SOURCES] * 10 + st[CA_S_UNREADABLE], 40, ctr)
456 gv_check_eq("F3-three-chart-emitters" as *u8, st[CA_S_EMITTERS], 3, ctr)
457 gv_check_eq("F3a-partition-emitters-equal-adopters-plus-hand-plus-hand-colour" as *u8, st[CA_S_ADOPTERS] + st[CA_S_HAND] + st[CA_S_HAND_COLOUR], st[CA_S_EMITTERS], ctr)
458 gv_check_eq("F4-the-icon-is-counted-not-judged" as *u8, st[CA_S_ICONS], 1, ctr)
459 gv_check_eq("F4a-the-icon-function-is-not-an-emitter" as *u8, ca_verdict_of(em_names, em_verd, em_verd[CA_MAX_EMIT], "icon_nav" as *u8), CA_NONE, ctr)
460 gv_check_eq("F5-the-ruler-caller-is-an-adopter" as *u8, ca_verdict_of(em_names, em_verd, em_verd[CA_MAX_EMIT], "ok_chart" as *u8), CA_ADOPTER, ctr)
461 gv_check_eq("F5a-a-url-before-the-tag-does-not-hide-it" as *u8, st[CA_S_ADOPTERS], 1, ctr)
462 gv_check_eq("F6-inline-arithmetic-is-hand" as *u8, ca_verdict_of(em_names, em_verd, em_verd[CA_MAX_EMIT], "hand_chart" as *u8), CA_HAND, ctr)
463 gv_check_eq("F6a-a-comment-only-mention-is-nothing" as *u8, ca_verdict_of(em_names, em_verd, em_verd[CA_MAX_EMIT], "note_only" as *u8), CA_NONE, ctr)
464 gv_check_eq("F7-a-literal-fill-beside-the-ruler-is-hand-colour" as *u8, ca_verdict_of(em_names, em_verd, em_verd[CA_MAX_EMIT], "col_chart" as *u8), CA_HAND_COLOUR, ctr)
465 gv_check_eq("F8-two-offenders-with-no-allow-rows" as *u8, st[CA_S_OFFENDERS], 2, ctr)
466 gv_check_eq("F8a-nothing-allowed-nothing-stale" as *u8, st[CA_S_ALLOWED] + st[CA_S_STALE_ALLOW], 0, ctr)
467 // an allow row with a reason exempts exactly that function
468 gv_check_eq("F9-allow-conf-written" as *u8, ca_fx_conf(CA_FX_CONF_ALLOW, "allow|/tmp/nx_chartlay_adoption_gate/hand.nx|hand_chart|a planted exemption with its reason\n" as *u8), 0, ctr)
469 gv_check_eq("F9a-allow-census-runs" as *u8, cl_adoption_gate(CA_FX_CONF_ALLOW, st, em_names, em_verd), 0, ctr)
470 gv_check_eq("F9b-the-allowed-function-is-exempt" as *u8, st[CA_S_ALLOWED], 1, ctr)
471 gv_check_eq("F9c-one-offender-remains" as *u8, st[CA_S_OFFENDERS], 1, ctr)
472 gv_check_eq("F9d-a-used-allow-row-is-not-stale" as *u8, st[CA_S_STALE_ALLOW], 0, ctr)
473 // NEG: an allow row for a function that is not an offender is stale and must read RED
474 gv_check_eq("F10-stale-conf-written" as *u8, ca_fx_conf(CA_FX_CONF_STALE, "allow|/tmp/nx_chartlay_adoption_gate/adopter.nx|ok_chart|an exemption nobody needs\n" as *u8), 0, ctr)
475 gv_check_eq("F10a-stale-census-runs" as *u8, cl_adoption_gate(CA_FX_CONF_STALE, st, em_names, em_verd), 0, ctr)
476 gv_check_eq("neg-control-F10b-an-allow-row-for-an-adopter-is-stale" as *u8, st[CA_S_STALE_ALLOW], 1, ctr)
477 gv_check_eq("neg-control-F10c-and-it-exempts-nothing" as *u8, st[CA_S_ALLOWED] * 10 + st[CA_S_OFFENDERS], 2, ctr)
478 gv_check_eq("neg-control-F10d-an-unreadable-conf-is-refused" as *u8, cl_adoption_gate("/tmp/nx_chartlay_adoption_gate/absent.conf" as *u8, st, em_names, em_verd), -1, ctr)
479 // ---- the live fleet: the compare generators named in cl_adoption.conf ----
480 var conf: *u8 = CA_CONF
481 if ca_exists(conf) == 0 { conf = CA_CONF_BR }
482 gv_need("L0-cl_adoption.conf-present" as *u8, ca_exists(conf), ctr)
483 ca_put(1, "LIVE conf=" as *u8); ca_put(1, conf); ca_put(1, "\n" as *u8)
484 gv_check_eq("L1-live-census-runs" as *u8, cl_adoption_gate(conf, st, em_names, em_verd), 0, ctr)
485 gv_check("L2-at-least-one-source-row" as *u8, (st[CA_S_SOURCES] > 0) as i64, ctr)
486 gv_check_eq("L3-every-source-readable" as *u8, st[CA_S_UNREADABLE], 0, ctr)
487 gv_check("L4-ruler-verbs-declared" as *u8, (st[CA_S_RULERS] > 0) as i64, ctr)
488 gv_check("L5-the-fleet-emits-at-least-one-chart" as *u8, (st[CA_S_EMITTERS] > 0) as i64, ctr)
489 gv_check_eq("L5a-partition-sums" as *u8, st[CA_S_ADOPTERS] + st[CA_S_HAND] + st[CA_S_HAND_COLOUR], st[CA_S_EMITTERS], ctr)
490 gv_check_eq("L6-the-position-map-figure-is-an-adopter" as *u8, ca_verdict_of(em_names, em_verd, em_verd[CA_MAX_EMIT], "cdp_fig_open" as *u8), CA_ADOPTER, ctr)
491 gv_check_eq("L7-no-unallowed-hand-chart-in-the-fleet" as *u8, st[CA_S_OFFENDERS], 0, ctr)
492 gv_check_eq("L8-no-stale-allow-row" as *u8, st[CA_S_STALE_ALLOW], 0, ctr)
493 gv_check_eq("L9-emitter-table-not-truncated" as *u8, st[CA_S_EMIT_TRUNC], 0, ctr)
494 gv_kv("live_sources" as *u8, st[CA_S_SOURCES])
495 gv_kv("live_functions" as *u8, st[CA_S_FUNCS])
496 gv_kv("live_emitters" as *u8, st[CA_S_EMITTERS])
497 gv_kv("live_adopters" as *u8, st[CA_S_ADOPTERS])
498 gv_kv("live_hand" as *u8, st[CA_S_HAND])
499 gv_kv("live_hand_colour" as *u8, st[CA_S_HAND_COLOUR])
500 gv_kv("live_icons" as *u8, st[CA_S_ICONS])
501 gv_kv("live_allowed" as *u8, st[CA_S_ALLOWED])
502 gv_kv("live_stale_allow" as *u8, st[CA_S_STALE_ALLOW])
503 gv_kv("live_offenders" as *u8, st[CA_S_OFFENDERS])
504 return gv_verdict("nx_chartlay_adoption_gate" as *u8, ctr, "the fleet ratchet for the one chart-layout ruler: every svg-emitting function in the compare generators named and judged (adopter, hand, hand-colour; icons counted, comments nothing), allow rows exempt with a reason and go stale when unneeded, planted fixtures prove every class both ways, and the live fleet carries no unallowed hand chart" as *u8)
505}