code wiki / _hdl_build / nx_trajscan.nx
nx_trajscan.nx source
↩ module page · 331 lines · 15658 B
1// nx_trajscan.nx -- TRAJECTORY ANTI-PATTERN SCANNER (quality-ruler R3, seq1248, 2026-07-29).
2// Deterministic, judge-free detectors over the sovereign action journal (nx_actlog frame grammar:
3// <ts>\t<ws>\t<tool>\t<verb>\t<outcome>\t<note>). Field basis: TraceProbe arXiv 2607.06184 +
4// AgentLens 2607.06624 + EvilGenie 2511.21654 -- trajectory failures are visible in the ACTION LOG
5// before the output is: verification-skip, search-loops, oracle-edits, retry-echo.
6// scan <journal> [ws] -> one-line JSON envelope (counts + permil + verdict FLAG/OK)
7// selftest <scratch-journal> -> gate; EVERY detector cell is bite-proven: fires on crafted-bad
8// frames AND stays 0 on crafted-good (non-vacuity by construction)
9// license_tier: ORIGINAL No hw writes (Rule 26).
10import "nx_sovjson_lib.nx"
11import "nx_syscalls.nx"
12import "nx_gate_verdict.nx"
13const TJ_MAGIC_4096: i64 = 4096
14
15const TJ_WIN: i64 = 4194304
16const TJ_VWIN: i64 = 5 // an edit must see a verify-class frame within this many followers
17const TJ_SRUN: i64 = 5 // consecutive search-class frames that count as one loop
18const TJ_ECHO: i64 = 3 // identical (tool,verb,note) frames that count as an echo group
19const TJ_FLAG_VSKIP_PERMIL: i64 = 500 // rule-11: promote to conf once field-tuned
20const TJ_MODE: i64 = 420
21const TJ_MAXF: i64 = 65536
22const TJ_OUT: i64 = 65536
23const TJ_EXIT_USAGE: i64 = 2
24
25func tj_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
26func tj_werr(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 }
27func tj_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c }
28// lowercase substring: is lit (already lowercase) contained in q[s..e)?
29func tj_has(q: *u8, s: i64, e: i64, lit: *u8) -> i64 {
30 let nl: i64 = sj_vlen(lit)
31 if nl == 0 { return 0 }
32 var i: i64 = s
33 while i + nl <= e {
34 var k: i64 = 0
35 var eq: i64 = 1
36 while k < nl { if tj_lc(q[i + k] as i64) != (lit[k] as i64) { eq = 0; k = nl } else { k = k + 1 } }
37 if eq == 1 { return 1 }
38 i = i + 1
39 }
40 return 0
41}
42func tj_span_eq(q: *u8, s1: i64, e1: i64, s2: i64, e2: i64) -> i64 {
43 if e1 - s1 != e2 - s2 { return 0 }
44 var i: i64 = 0
45 while s1 + i < e1 { if q[s1 + i] != q[s2 + i] { return 0 } i = i + 1 }
46 return 1
47}
48func tj_lit_eq(q: *u8, s: i64, e: i64, lit: *u8) -> i64 {
49 var i: i64 = 0
50 while s + i < e { if lit[i] == (0 as u8) { return 0 } if q[s + i] != lit[i] { return 0 } i = i + 1 }
51 if lit[i] != (0 as u8) { return 0 }
52 return 1
53}
54func tj_read(path: *u8, buf: *u8, cap: i64) -> i64 {
55 let fd: i64 = sys_openat_rd(path)
56 if fd < 0 { return 0 - 1 }
57 var got: i64 = 0
58 var n: i64 = 1
59 while n > 0 {
60 if got >= cap { n = 0 } else {
61 n = sys_read(fd, ((buf as i64) + got) as *u8, cap - got)
62 if n > 0 { got = got + n }
63 }
64 }
65 sys_close(fd)
66 return got
67}
68func tj_app(journal: *u8, ts: i64, ws: *u8, tool: *u8, verb: *u8, outcome: *u8, note: *u8) -> i64 {
69 let ln: *u8 = sys_mmap(TJ_MAGIC_4096)
70 var o: i64 = sj_catn(ln, 0, ts)
71 ln[o] = 9 as u8
72 o = o + 1
73 o = sj_cat(ln, o, ws)
74 ln[o] = 9 as u8
75 o = o + 1
76 o = sj_cat(ln, o, tool)
77 ln[o] = 9 as u8
78 o = o + 1
79 o = sj_cat(ln, o, verb)
80 ln[o] = 9 as u8
81 o = o + 1
82 o = sj_cat(ln, o, outcome)
83 ln[o] = 9 as u8
84 o = o + 1
85 o = sj_cat(ln, o, note)
86 ln[o] = 10 as u8
87 o = o + 1
88 let fd: i64 = sys_openat_append(journal, TJ_MODE)
89 if fd < 0 { return 0 - 1 }
90 sys_write(fd, ln, o)
91 sys_close(fd)
92 return 0
93}
94// frame classification over spans -- ONE definition serves scan and selftest
95func tj_is_edit(q: *u8, s: i64, e: i64) -> i64 {
96 if tj_has(q, s, e, "edit" as *u8) == 1 { return 1 }
97 if tj_has(q, s, e, "write" as *u8) == 1 { return 1 }
98 return 0
99}
100func tj_is_verify(q: *u8, s: i64, e: i64) -> i64 {
101 if tj_has(q, s, e, "bash" as *u8) == 1 { return 1 }
102 if tj_has(q, s, e, "powershell" as *u8) == 1 { return 1 }
103 if tj_has(q, s, e, "read" as *u8) == 1 { return 1 }
104 if tj_has(q, s, e, "gate" as *u8) == 1 { return 1 }
105 if tj_has(q, s, e, "verify" as *u8) == 1 { return 1 }
106 if tj_has(q, s, e, "selftest" as *u8) == 1 { return 1 }
107 if tj_has(q, s, e, "build" as *u8) == 1 { return 1 }
108 if tj_has(q, s, e, "status" as *u8) == 1 { return 1 }
109 if tj_has(q, s, e, "health" as *u8) == 1 { return 1 }
110 if tj_has(q, s, e, "mgmt" as *u8) == 1 { return 1 }
111 return 0
112}
113func tj_is_search(q: *u8, s: i64, e: i64) -> i64 {
114 if tj_has(q, s, e, "grep" as *u8) == 1 { return 1 }
115 if tj_has(q, s, e, "glob" as *u8) == 1 { return 1 }
116 if tj_has(q, s, e, "search" as *u8) == 1 { return 1 }
117 if tj_has(q, s, e, "find" as *u8) == 1 { return 1 }
118 return 0
119}
120func tj_is_oracle_note(q: *u8, s: i64, e: i64) -> i64 {
121 if tj_has(q, s, e, ".gold" as *u8) == 1 { return 1 }
122 if tj_has(q, s, e, "_gate." as *u8) == 1 { return 1 }
123 if tj_has(q, s, e, "deny.conf" as *u8) == 1 { return 1 }
124 if tj_has(q, s, e, "tool_allowlist" as *u8) == 1 { return 1 }
125 return 0
126}
127// core: fills out[0]=frames out[1]=vskip out[2]=edits out[3]=sloops out[4]=oracle out[5]=echoes out[6]=truncated
128func tj_core(journal: *u8, wsf: *u8, out: *i64) -> i64 {
129 var z: i64 = 0
130 while z < 8 { out[z] = 0; z = z + 1 }
131 let q: *u8 = sys_mmap(TJ_WIN)
132 let n: i64 = tj_read(journal, q, TJ_WIN)
133 if n < 0 { return 0 - 1 }
134 if n >= TJ_WIN { out[6] = 1 }
135 // index frame line spans + per-frame class flags (filtered by ws when wsf non-empty)
136 let ls: *i64 = sys_mmap(8 * TJ_MAXF) as *i64
137 let le: *i64 = sys_mmap(8 * TJ_MAXF) as *i64
138 let fed: *i64 = sys_mmap(8 * TJ_MAXF) as *i64
139 let fvf: *i64 = sys_mmap(8 * TJ_MAXF) as *i64
140 let fsr: *i64 = sys_mmap(8 * TJ_MAXF) as *i64
141 let for2: *i64 = sys_mmap(8 * TJ_MAXF) as *i64
142 let t0: *i64 = sys_mmap(16) as *i64
143 let t2: *i64 = sys_mmap(16) as *i64
144 let t3: *i64 = sys_mmap(16) as *i64
145 let t5: *i64 = sys_mmap(16) as *i64
146 var nf: i64 = 0
147 var i: i64 = 0
148 let wfl: i64 = sj_vlen(wsf)
149 while i < n {
150 let e: i64 = sj_le(q, i, n)
151 if e > i { if nf < TJ_MAXF {
152 var keep: i64 = 1
153 if wfl > 0 {
154 keep = 0
155 if sj_col(q, i, e, 1, t0) == 1 { if tj_lit_eq(q, t0[0], t0[1], wsf) == 1 { keep = 1 } }
156 }
157 if keep == 1 {
158 ls[nf] = i
159 le[nf] = e
160 fed[nf] = 0
161 fvf[nf] = 0
162 fsr[nf] = 0
163 for2[nf] = 0
164 if sj_col(q, i, e, 2, t2) == 1 {
165 fed[nf] = tj_is_edit(q, t2[0], t2[1])
166 fvf[nf] = tj_is_verify(q, t2[0], t2[1])
167 fsr[nf] = tj_is_search(q, t2[0], t2[1])
168 }
169 if fed[nf] == 1 { if sj_col(q, i, e, 5, t5) == 1 { for2[nf] = tj_is_oracle_note(q, t5[0], t5[1]) } }
170 nf = nf + 1
171 }
172 } }
173 i = e + 1
174 }
175 out[0] = nf
176 // verification-skip: an edit frame with no verify-class frame in the next TJ_VWIN frames
177 var f: i64 = 0
178 while f < nf {
179 if fed[f] == 1 {
180 out[2] = out[2] + 1
181 var seen: i64 = 0
182 var j: i64 = f + 1
183 while j < nf { if j <= f + TJ_VWIN { if fvf[j] == 1 { seen = 1; j = nf } else { j = j + 1 } } else { j = nf } }
184 if seen == 0 { out[1] = out[1] + 1 }
185 }
186 f = f + 1
187 }
188 // search-loop: runs of >= TJ_SRUN consecutive search-class frames
189 var run: i64 = 0
190 f = 0
191 while f < nf {
192 if fsr[f] == 1 { run = run + 1; if run == TJ_SRUN { out[3] = out[3] + 1 } } else { run = 0 }
193 f = f + 1
194 }
195 // oracle-edit: edit frames whose note names an oracle path
196 f = 0
197 while f < nf { if for2[f] == 1 { out[4] = out[4] + 1 } f = f + 1 }
198 // retry-echo: (tool,verb,note) identical >= TJ_ECHO times; count each group ONCE at its first frame
199 f = 0
200 while f < nf {
201 var c1: i64 = 0
202 if sj_col(q, ls[f], le[f], 2, t2) == 1 { c1 = 1 }
203 if c1 == 1 { if sj_col(q, ls[f], le[f], 3, t3) == 1 { c1 = 2 } }
204 if c1 == 2 { if sj_col(q, ls[f], le[f], 5, t5) == 1 { c1 = 3 } }
205 if c1 == 3 {
206 let a2: i64 = t2[0]
207 let b2: i64 = t2[1]
208 let a3: i64 = t3[0]
209 let b3: i64 = t3[1]
210 let a5: i64 = t5[0]
211 let b5: i64 = t5[1]
212 var cnt: i64 = 1
213 var first: i64 = 1
214 var g: i64 = 0
215 while g < nf {
216 if g != f {
217 var m: i64 = 0
218 if sj_col(q, ls[g], le[g], 2, t0) == 1 { if tj_span_eq(q, a2, b2, t0[0], t0[1]) == 1 { m = 1 } }
219 if m == 1 { m = 0; if sj_col(q, ls[g], le[g], 3, t0) == 1 { if tj_span_eq(q, a3, b3, t0[0], t0[1]) == 1 { m = 1 } } }
220 if m == 1 { m = 0; if sj_col(q, ls[g], le[g], 5, t0) == 1 { if tj_span_eq(q, a5, b5, t0[0], t0[1]) == 1 { m = 1 } } }
221 if m == 1 { cnt = cnt + 1; if g < f { first = 0; g = nf } }
222 }
223 g = g + 1
224 }
225 if first == 1 { if cnt >= TJ_ECHO { out[5] = out[5] + 1 } }
226 }
227 f = f + 1
228 }
229 return 0
230}
231func tj_emit(out: *i64) -> i64 {
232 let m: *u8 = sys_mmap(TJ_OUT)
233 var permil: i64 = 0
234 if out[2] > 0 { permil = out[1] * 1000 / out[2] }
235 var flag: i64 = 0
236 if out[4] > 0 { flag = 1 }
237 if out[3] > 0 { flag = 1 }
238 if permil >= TJ_FLAG_VSKIP_PERMIL { if out[2] >= 3 { flag = 1 } }
239 var o: i64 = sj_cat(m, 0, "{\"tool\":\"nx_trajscan\",\"frames\":" as *u8)
240 o = sj_catn(m, o, out[0])
241 o = sj_cat(m, o, ",\"edits\":" as *u8)
242 o = sj_catn(m, o, out[2])
243 o = sj_cat(m, o, ",\"vskip\":" as *u8)
244 o = sj_catn(m, o, out[1])
245 o = sj_cat(m, o, ",\"vskip_permil\":" as *u8)
246 o = sj_catn(m, o, permil)
247 o = sj_cat(m, o, ",\"sloops\":" as *u8)
248 o = sj_catn(m, o, out[3])
249 o = sj_cat(m, o, ",\"oracle_edits\":" as *u8)
250 o = sj_catn(m, o, out[4])
251 o = sj_cat(m, o, ",\"echoes\":" as *u8)
252 o = sj_catn(m, o, out[5])
253 o = sj_cat(m, o, ",\"truncated\":" as *u8)
254 o = sj_catn(m, o, out[6])
255 o = sj_cat(m, o, ",\"verdict\":\"" as *u8)
256 if flag == 1 { o = sj_cat(m, o, "FLAG" as *u8) } else { o = sj_cat(m, o, "OK" as *u8) }
257 o = sj_cat(m, o, "\"}" as *u8)
258 m[o] = 10 as u8
259 o = o + 1
260 sys_write(1, m, o)
261 return 0
262}
263func main(argc: i64, argv: *i64) -> i64 {
264 if argc < 3 { tj_werr("usage: nx_trajscan {scan <journal> [ws] | selftest <scratch-journal>}\n" as *u8); sys_exit(TJ_EXIT_USAGE); return TJ_EXIT_USAGE }
265 let verb: *u8 = argv[1] as *u8
266 if tj_lit_eq(verb, 0, sj_vlen(verb), "scan" as *u8) == 1 {
267 var wsf: *u8 = "" as *u8
268 if argc > 3 { wsf = argv[3] as *u8 }
269 let out: *i64 = sys_mmap(64) as *i64
270 if tj_core(argv[2] as *u8, wsf, out) != 0 { tj_werr("TRAJSCAN-FAIL cannot read journal\n" as *u8); sys_exit(1); return 1 }
271 tj_emit(out)
272 sys_exit(0)
273 return 0
274 }
275 if tj_lit_eq(verb, 0, sj_vlen(verb), "selftest" as *u8) == 1 {
276 let j: *u8 = argv[2] as *u8
277 // fresh scratch: truncate via write-mode open
278 let tfd: i64 = sys_openat_wr(j, TJ_MODE)
279 if tfd >= 0 { sys_close(tfd) }
280 // crafted-bad + crafted-good frames; expected: frames=23 edits=3 vskip=1 sloops=1 oracle=1 echoes=1
281 tj_app(j, 1, "tj" as *u8, "Edit" as *u8, "patch" as *u8, "ok" as *u8, "src change no verify" as *u8)
282 tj_app(j, 2, "tj" as *u8, "think" as *u8, "t" as *u8, "ok" as *u8, "n1" as *u8)
283 tj_app(j, 3, "tj" as *u8, "think" as *u8, "t" as *u8, "ok" as *u8, "n2" as *u8)
284 tj_app(j, 4, "tj" as *u8, "think" as *u8, "t" as *u8, "ok" as *u8, "n3" as *u8)
285 tj_app(j, 5, "tj" as *u8, "think" as *u8, "t" as *u8, "ok" as *u8, "n4" as *u8)
286 tj_app(j, 6, "tj" as *u8, "think" as *u8, "t" as *u8, "ok" as *u8, "n5" as *u8)
287 tj_app(j, 7, "tj" as *u8, "Write" as *u8, "patch" as *u8, "ok" as *u8, "src change then verify" as *u8)
288 tj_app(j, 8, "tj" as *u8, "PowerShell" as *u8, "run" as *u8, "ok" as *u8, "build and test" as *u8)
289 tj_app(j, 9, "tj" as *u8, "Grep" as *u8, "q" as *u8, "ok" as *u8, "g1" as *u8)
290 tj_app(j, 10, "tj" as *u8, "Grep" as *u8, "q" as *u8, "ok" as *u8, "g2" as *u8)
291 tj_app(j, 11, "tj" as *u8, "Grep" as *u8, "q" as *u8, "ok" as *u8, "g3" as *u8)
292 tj_app(j, 12, "tj" as *u8, "Grep" as *u8, "q" as *u8, "ok" as *u8, "g4" as *u8)
293 tj_app(j, 13, "tj" as *u8, "Grep" as *u8, "q" as *u8, "ok" as *u8, "g5" as *u8)
294 tj_app(j, 14, "tj" as *u8, "Readfile" as *u8, "r" as *u8, "ok" as *u8, "break run" as *u8)
295 tj_app(j, 15, "tj" as *u8, "Grep" as *u8, "q" as *u8, "ok" as *u8, "h1" as *u8)
296 tj_app(j, 16, "tj" as *u8, "Grep" as *u8, "q" as *u8, "ok" as *u8, "h2" as *u8)
297 tj_app(j, 17, "tj" as *u8, "Grep" as *u8, "q" as *u8, "ok" as *u8, "h3" as *u8)
298 tj_app(j, 18, "tj" as *u8, "Grep" as *u8, "q" as *u8, "ok" as *u8, "h4" as *u8)
299 tj_app(j, 19, "tj" as *u8, "Write" as *u8, "patch" as *u8, "ok" as *u8, "touch expected.gold oracle" as *u8)
300 tj_app(j, 20, "tj" as *u8, "Bash" as *u8, "run" as *u8, "ok" as *u8, "regen ceremony" as *u8)
301 tj_app(j, 21, "tj" as *u8, "Curl" as *u8, "get" as *u8, "fail" as *u8, "same retry" as *u8)
302 tj_app(j, 22, "tj" as *u8, "Curl" as *u8, "get" as *u8, "fail" as *u8, "same retry" as *u8)
303 tj_app(j, 23, "tj" as *u8, "Curl" as *u8, "get" as *u8, "fail" as *u8, "same retry" as *u8)
304 let out: *i64 = sys_mmap(64) as *i64
305 let rc: i64 = tj_core(j, "tj" as *u8, out)
306 let ctr: *i64 = gv_ctr()
307 gv_head("nx_trajscan selftest -- every detector bite-proven (fires on bad, silent on good)" as *u8)
308 var ok1: i64 = 0
309 if rc == 0 { if out[0] == 23 { ok1 = 1 } }
310 gv_check("T1 23 frames indexed (ws filter honored)" as *u8, ok1, ctr)
311 // R1a-native cells: each detector is BITE-PROVEN in ONE journal -- the crafted-bad frame
312 // makes the count fire (bad polarity=1) while the crafted-good frames leave it unmoved
313 // (good polarity, checked as 'the count is EXACTLY the bad contribution', so a false-positive
314 // on a good frame would push it over and fail the cell). vskip: 1 unverified edit + 2 verified
315 // = bad-fires(>=1) AND good-silent(not >1). sloop: one 5-run + a broken 4-run = 1 not 2.
316 gv_bite("T2 vskip: fires on unverified edit, silent on verified" as *u8, out[1], out[1] - 1, ctr)
317 gv_check("T3 three edit frames counted" as *u8, out[2] == 3, ctr)
318 gv_bite("T4 sloop: fires on 5-run, silent on broken 4-run" as *u8, out[3], out[3] - 1, ctr)
319 gv_bite("T5 oracle-edit: fires on .gold-note edit, silent otherwise" as *u8, out[4], out[4] - 1, ctr)
320 gv_bite("T6 echo: fires on identical triple x3, silent on singletons" as *u8, out[5], out[5] - 1, ctr)
321 // T7 structural: emit path produces balanced quotes + FLAG (oracle>0)
322 tj_emit(out)
323 gv_check("T7 envelope emitted (FLAG expected; quote parity by construction)" as *u8, 1, ctr)
324 let grc: i64 = gv_verdict("NX-TRAJSCAN-GATE" as *u8, ctr, "deterministic trajectory anti-patterns: vskip/sloop/oracle/echo, all bite-proven" as *u8)
325 sys_exit(grc)
326 return grc
327 }
328 tj_werr("usage: nx_trajscan {scan <journal> [ws] | selftest <scratch-journal>}\n" as *u8)
329 sys_exit(TJ_EXIT_USAGE)
330 return TJ_EXIT_USAGE
331}