code wiki / _hdl_build / nx_treediverge_beat.nx
nx_treediverge_beat.nx source
↩ module page · 168 lines · 8026 B
1// nx_treediverge_beat.nx -- no-arg BEAT for the ONE clock: the standing cross-tree divergence gauge
2// (debt 1785622769: divergence must be caught the day it appears, not by archaeology). Composes the
3// three PROVEN instruments through tr_run_capture -- zero detector logic duplicated: nx_treediff
4// regenerates the NAS manifest, nx_treediverge compares it against the authoring manifest (pushed
5// fresh at every session start by the treecanon SessionStart hook), nx_treecanon_gate re-runs the
6// canon verdict. APPENDS one evidence line per run to the trend log -- including BEAT-FAIL lines
7// (a beat with no evidence line is a claim; failure lines are evidence too). The BURN-DOWN is the
8// point: divergent= over time IS the 8,258-file fork shrinking, minable from the trend file.
9// Scheduler contract: the tickless clock (clock_jobs.tsv / the clockjobs- store plane) fork+execs
10// this elf with NO args from cwd nishihost. argv[1]=authoring-manifest argv[2]=trendfile override
11// the defaults for testing (argv[1] pointed at a missing file is the negative control:
12// nx_treediverge REFUSES by its own non-vacuity law and this beat must append BEAT-FAIL + exit 1,
13// never a comforting line).
14// trend line: <epoch>\tDIVERGE <envelope, newlines as ' | '>\tGATE rc=<rc> <verdict line>\n
15// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
16import "nx_sovjson_lib.nx"
17import "nx_syscalls.nx"
18import "nx_tool_run.nx"
19
20const TDB_DIVCAP: i64 = 4194304 // treediverge prints a row per divergent file (8,258 today, ~40B
21 // each = ~350KB); 4MB holds ~100k rows. AT the cap = truncated =
22 // the envelope (printed LAST) may be gone -> BEAT-FAIL, because a
23 // capped result is a FLOOR, never a count.
24const TDB_SMALLCAP: i64 = 16384
25const TDB_LINE: i64 = 4096
26const TDB_MODE: i64 = 420
27const TDB_TAB: i64 = 9
28const TDB_NL: i64 = 10
29const TDB_SPACE: i64 = 32
30const TDB_PIPE: i64 = 124
31
32func tdb_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
33func tdb_werr(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 }
34func tdb_append(path: *u8, buf: *u8, n: i64) -> i64 {
35 let fd: i64 = sys_openat_append(path, TDB_MODE)
36 if fd < 0 { return 0 - 1 }
37 sys_write(fd, buf, n)
38 sys_fsync(fd)
39 sys_close(fd)
40 return 0
41}
42// end index after stripping trailing newlines
43func tdb_tail_end(buf: *u8, n: i64) -> i64 {
44 var e: i64 = n
45 var go: i64 = 1
46 while go == 1 { if e > 0 { if buf[e-1] == (TDB_NL as u8) { e = e - 1 } else { go = 0 } } else { go = 0 } }
47 return e
48}
49// start index of the `want`-th line counting back from the (newline-stripped) end; 0-bounded
50func tdb_tail_start(buf: *u8, n: i64, want: i64) -> i64 {
51 let e: i64 = tdb_tail_end(buf, n)
52 var i: i64 = e
53 var cnt: i64 = 0
54 var go: i64 = 1
55 while go == 1 {
56 if i <= 0 { go = 0 } else {
57 if buf[i-1] == (TDB_NL as u8) { cnt = cnt + 1; if cnt >= want { go = 0 } }
58 if go == 1 { i = i - 1 }
59 }
60 }
61 return i
62}
63func tdb_contains(buf: *u8, n: i64, pat: *u8) -> i64 {
64 var pl: i64 = 0
65 while pat[pl] != (0 as u8) { pl = pl + 1 }
66 if pl == 0 { return 0 }
67 var i: i64 = 0
68 while i + pl <= n {
69 var j: i64 = 0
70 var hit: i64 = 1
71 while j < pl { if buf[i+j] != pat[j] { hit = 0; j = pl } else { j = j + 1 } }
72 if hit == 1 { return 1 }
73 i = i + 1
74 }
75 return 0
76}
77// copy [s,e) of buf into ln at o, rendering newlines as ' | '; bounded by TDB_LINE
78func tdb_cat_span(ln: *u8, o: i64, buf: *u8, s: i64, e: i64) -> i64 {
79 var p: i64 = o
80 var i: i64 = s
81 while i < e {
82 if p >= TDB_LINE - 4 { i = e } else {
83 if buf[i] == (TDB_NL as u8) { ln[p] = TDB_SPACE as u8; ln[p+1] = TDB_PIPE as u8; ln[p+2] = TDB_SPACE as u8; p = p + 3 }
84 else { ln[p] = buf[i]; p = p + 1 }
85 i = i + 1
86 }
87 }
88 return p
89}
90
91func main(argc: i64, argv: *i64) -> i64 {
92 var lapmf: *u8 = "knowledge/status/treecanon_laptop.mf" as *u8
93 var trend: *u8 = "knowledge/status/treediverge_trend.log" as *u8
94 if argc > 1 { lapmf = argv[1] as *u8 }
95 if argc > 2 { trend = argv[2] as *u8 }
96 let nasmf: *u8 = "knowledge/status/treecanon_nas.mf" as *u8
97 let conf: *u8 = "knowledge/tree_canon.conf" as *u8
98 let e_td: *u8 = "/volume1/homes/elderwesto/nishihost/nx_treediff.elf" as *u8
99 let e_dv: *u8 = "/volume1/homes/elderwesto/nishihost/nx_treediverge.elf" as *u8
100 let e_gt: *u8 = "/volume1/homes/elderwesto/nishihost/nx_treecanon_gate.elf" as *u8
101
102 // 1) fresh NAS manifest (also a durable artifact other instruments can read)
103 let av1: *i64 = sys_mmap(8*4) as *i64
104 av1[0] = e_td as i64; av1[1] = ("buildroot/runtime" as *u8) as i64; av1[2] = nasmf as i64; av1[3] = 0
105 let out1: *u8 = sys_mmap(TDB_SMALLCAP)
106 let ol1: *i64 = sys_mmap(16) as *i64
107 let rc1: i64 = tr_run_capture(e_td, av1, out1, TDB_SMALLCAP, ol1)
108
109 // 2) the divergence report (the burn-down number lives in its envelope, printed LAST)
110 let av2: *i64 = sys_mmap(8*6) as *i64
111 av2[0] = e_dv as i64; av2[1] = nasmf as i64; av2[2] = lapmf as i64
112 av2[3] = ("NAS" as *u8) as i64; av2[4] = ("LAPTOP" as *u8) as i64; av2[5] = 0
113 let out2: *u8 = sys_mmap(TDB_DIVCAP)
114 let ol2: *i64 = sys_mmap(16) as *i64
115 let rc2: i64 = tr_run_capture(e_dv, av2, out2, TDB_DIVCAP, ol2)
116
117 // 3) the canon verdict (RED here is EVIDENCE, not a beat failure -- the build lane enforces;
118 // this beat only measures, so its own exit stays 0 as long as the evidence was captured)
119 let av3: *i64 = sys_mmap(8*5) as *i64
120 av3[0] = e_gt as i64; av3[1] = nasmf as i64; av3[2] = lapmf as i64; av3[3] = conf as i64; av3[4] = 0
121 let out3: *u8 = sys_mmap(TDB_SMALLCAP)
122 let ol3: *i64 = sys_mmap(16) as *i64
123 let rc3: i64 = tr_run_capture(e_gt, av3, out3, TDB_SMALLCAP, ol3)
124
125 var ok: i64 = 1
126 if rc1 != 0 { ok = 0 }
127 if ol2[0] <= 0 { ok = 0 }
128 if ol2[0] >= TDB_DIVCAP - 1 { ok = 0 }
129 if tdb_contains(out2, ol2[0], "verdict=" as *u8) == 0 { ok = 0 }
130 // MEASURED (first negative-control run): a REFUSED report also prints "verdict=REFUSED", so
131 // contains("verdict=") alone waved a blind gauge through as OK. treediverge's exit codes are
132 // rc=1 for a successful DIVERGENT compare and rc=3 for REFUSED, so rc can't be the ok-signal
133 // either without baking in its full code table -- anchor on the tool's own refusal vocabulary.
134 if tdb_contains(out2, ol2[0], "verdict=REFUSED" as *u8) == 1 { ok = 0 }
135 if ol3[0] <= 0 { ok = 0 }
136
137 let ln: *u8 = sys_mmap(TDB_LINE)
138 var o: i64 = sj_catn(ln, 0, sys_now_realtime_sec())
139 ln[o] = TDB_TAB as u8; o = o + 1
140 if ok == 1 {
141 o = sj_cat(ln, o, "DIVERGE " as *u8)
142 o = tdb_cat_span(ln, o, out2, tdb_tail_start(out2, ol2[0], 3), tdb_tail_end(out2, ol2[0]))
143 ln[o] = TDB_TAB as u8; o = o + 1
144 o = sj_cat(ln, o, "GATE rc=" as *u8)
145 o = sj_catn(ln, o, rc3)
146 ln[o] = TDB_SPACE as u8; o = o + 1
147 o = tdb_cat_span(ln, o, out3, tdb_tail_start(out3, ol3[0], 1), tdb_tail_end(out3, ol3[0]))
148 } else {
149 o = sj_cat(ln, o, "BEAT-FAIL treediff_rc=" as *u8)
150 o = sj_catn(ln, o, rc1)
151 o = sj_cat(ln, o, " diverge_rc=" as *u8)
152 o = sj_catn(ln, o, rc2)
153 o = sj_cat(ln, o, " diverge_outlen=" as *u8)
154 o = sj_catn(ln, o, ol2[0])
155 o = sj_cat(ln, o, " gate_rc=" as *u8)
156 o = sj_catn(ln, o, rc3)
157 }
158 ln[o] = TDB_NL as u8; o = o + 1
159 if tdb_append(trend, ln, o) != 0 { tdb_werr("TREEDIVERGE-BEAT FAIL cannot append trend file\n" as *u8); sys_exit(1); return 1 }
160 if ok == 1 {
161 tdb_puts("TREEDIVERGE-BEAT OK appended evidence line\n" as *u8)
162 sys_exit(0)
163 return 0
164 }
165 tdb_werr("TREEDIVERGE-BEAT FAIL (BEAT-FAIL line appended as evidence)\n" as *u8)
166 sys_exit(1)
167 return 1
168}