code wiki / _hdl_build / nx_gate_sentinel.nx
nx_gate_sentinel.nx source
↩ module page · 151 lines · 6655 B
1// nx_gate_sentinel.nx -- TEAM-OWNED watchdog over nx_game_gate (operator:
2// "keep building the team to handle the ask"). Runs the gate, compares the
3// fails count against the recorded baseline, and reacts WITHOUT Claude:
4// fails > baseline -> REGRESSION: escalate to the PM review log (pm_gap)
5// fails < baseline -> IMPROVEMENT: record a deliverable line
6// fails == baseline -> STEADY: quiet (one SENTINEL line in the gate log)
7// Baseline auto-updates after every report so each CHANGE escalates exactly
8// once (no spam, no silent drift). This is the loop-monitor pattern from
9// [[project-self-running-loop-governed-ingest-2026-06-05]] applied to the
10// game-engine substrate: the gate produces evidence, the sentinel watches it,
11// the PM log receives the escalation -- grades stay LIVE with no LLM in the
12// loop.
13//
14// Usage (from nxc2 repo root):
15// nx_gate_sentinel.elf # gate with default _offc compiler
16// nx_gate_sentinel.elf /path/to/nx_cc.elf # gate a candidate compiler
17// Exit: 0 steady/improvement/init, 1 regression, 2 gate-missing/crashed.
18//
19// license_tier: ORIGINAL
20import "nx_pm_review_log.nx"
21import "nx_syscalls.nx"
22
23func gs_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
24func gs_putn(v: i64) -> i64 { let bb: *u8 = sys_mmap(28); var m: i64=v; if m<0 {m=0-m;sys_write(1,"-" as *u8,1)}; let t: *u8 = sys_mmap(28); var k: i64=0; if m==0 {t[0]=48;k=1}; while m>0 {t[k]=48+(m%10); m=m/10; k=k+1}; var i: i64=0; while i<k {bb[i]=t[k-1-i]; i=i+1}; sys_write(1, bb, k); return 0 }
25func gs_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i }
26func gs_cat_n(dst: *u8, off: i64, v: i64) -> i64 {
27 var o: i64 = off
28 var m: i64 = v
29 if m < 0 { dst[o] = 45; o = o + 1; m = 0 - m }
30 let t: *u8 = sys_mmap(28)
31 var k: i64 = 0
32 if m == 0 { t[0] = 48; k = 1 }
33 while m > 0 { t[k] = 48 + (m % 10); m = m / 10; k = k + 1 }
34 var i: i64 = 0
35 while i < k { dst[o+i] = t[k-1-i]; i = i + 1 }
36 return o + k
37}
38
39// fork+exec the gate; returns its exit (=fails count), or 128+sig
40func gs_run_gate(path: *u8, arg1: *u8) -> i64 {
41 let pid: i64 = sys_fork()
42 if pid == 0 {
43 let devnull: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
44 if devnull >= 0 { sys_dup3(devnull, 1, 0) } // gate already logs to its own file
45 let argv: *i64 = sys_mmap(32) as *i64
46 argv[0] = path as i64; argv[1] = arg1 as i64; argv[2] = 0
47 let envp: *i64 = sys_mmap(16) as *i64
48 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0
49 sys_execve(path, argv, envp); sys_exit(127)
50 }
51 let st: *i64 = sys_mmap(16) as *i64
52 sys_wait4(pid, st, 0)
53 let sig: i64 = st[0] & 0x7f
54 if sig != 0 { return 128 + sig }
55 return (st[0] >> 8) & 0xff
56}
57
58// read baseline int from file; -1 if absent/unparseable
59func gs_read_baseline(path: *u8) -> i64 {
60 let len_p: *i64 = sys_mmap(8) as *i64
61 let buf: *u8 = sys_read_file(path, len_p)
62 if (buf as i64) == 0 { return 0 - 1 }
63 let n: i64 = len_p[0]
64 if n <= 0 { return 0 - 1 }
65 var v: i64 = 0
66 var any: i64 = 0
67 var i: i64 = 0
68 while i < n {
69 let c: i64 = buf[i] as i64
70 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1 } }
71 i = i + 1
72 }
73 if any == 0 { return 0 - 1 }
74 return v
75}
76
77func gs_write_baseline(path: *u8, v: i64) -> i64 {
78 let fd: i64 = sys_openat_wr(path, 0x1a4)
79 if fd < 0 { return 0 - 1 }
80 let buf: *u8 = sys_mmap(64)
81 var o: i64 = gs_cat_n(buf, 0, v)
82 buf[o] = 0x0A as u8
83 sys_write(fd, buf, o + 1)
84 sys_close(fd)
85 return 0
86}
87
88// append a SENTINEL line to the gate log
89func gs_log(kind: *u8, fails: i64, baseline: i64, ts: i64) -> i64 {
90 let fd: i64 = sys_openat_append("/tmp/nishi_game_gate.log" as *u8, 0x1a4)
91 let line: *u8 = sys_mmap(256)
92 var o: i64 = 0
93 o = gs_cat(line, o, "SENTINEL ts=" as *u8)
94 o = gs_cat_n(line, o, ts)
95 o = gs_cat(line, o, " kind=" as *u8)
96 o = gs_cat(line, o, kind)
97 o = gs_cat(line, o, " fails=" as *u8)
98 o = gs_cat_n(line, o, fails)
99 o = gs_cat(line, o, " baseline=" as *u8)
100 o = gs_cat_n(line, o, baseline)
101 o = gs_cat(line, o, "\n" as *u8)
102 sys_write(1, line, o)
103 if fd >= 0 { sys_write(fd, line, o); sys_close(fd) }
104 return 0
105}
106
107func main(argc: i64, argv: *i64) -> i64 {
108 let gate: *u8 = "/tmp/nx_game_gate.sov.elf" as *u8
109 var arg1: *u8 = 0 as *u8
110 if argc >= 2 { arg1 = argv[1] as *u8 }
111 let base_path: *u8 = "/tmp/nishi_game_gate.baseline" as *u8
112 let ts: i64 = sys_now_realtime_sec()
113
114 let fails: i64 = gs_run_gate(gate, arg1)
115 if fails >= 127 {
116 gs_log("GATE-CRASHED" as *u8, fails, 0 - 1, ts)
117 let pm0: i64 = pm_open("/tmp/nishi_pm_review.log" as *u8)
118 pm_gap(pm0, "game-engine/gates" as *u8, "NEEDS-FIX" as *u8, "gate crashed or missing" as *u8, "nx_gate_sentinel could not run /tmp/nx_game_gate.sov.elf (exit>=127); rebuild via nx_sov_build_run nx_game_gate" as *u8)
119 sys_close(pm0)
120 return 2
121 }
122
123 let baseline: i64 = gs_read_baseline(base_path)
124 if baseline < 0 {
125 gs_write_baseline(base_path, fails)
126 gs_log("INIT" as *u8, fails, fails, ts)
127 return 0
128 }
129 if fails > baseline {
130 gs_log("REGRESSION" as *u8, fails, baseline, ts)
131 let pm: i64 = pm_open("/tmp/nishi_pm_review.log" as *u8)
132 pm_gap(pm, "game-engine/gates" as *u8, "NEEDS-FIX" as *u8, "GAMEGATE REGRESSION" as *u8, "nx_gate_sentinel: gate fails count rose above baseline -- see GAMEGATE lines in /tmp/nishi_game_gate.log; per-module LOCALIZATION (assembler vs compiler/source) auto-appended to /tmp/nishi_game_gate_triage.log by nx_gate_triage" as *u8)
133 sys_close(pm)
134 gs_write_baseline(base_path, fails)
135 // auto-LOCALIZE: fork the triage so the escalation arrives with the
136 // oracle-differential classification attached (best-effort; a missing
137 // triage binary must not break the sentinel).
138 gs_run_gate("/tmp/nx_gate_triage.sov.elf" as *u8, arg1)
139 return 1
140 }
141 if fails < baseline {
142 gs_log("IMPROVEMENT" as *u8, fails, baseline, ts)
143 let pm2: i64 = pm_open("/tmp/nishi_pm_review.log" as *u8)
144 pm_deliverable(pm2, "game-engine/gates" as *u8, "gate-improvement" as *u8, "nx_gate_sentinel: gate fails count dropped below baseline (fix landed); baseline updated" as *u8)
145 sys_close(pm2)
146 gs_write_baseline(base_path, fails)
147 return 0
148 }
149 gs_log("STEADY" as *u8, fails, baseline, ts)
150 return 0
151}