nx_vizsla_notify_mcp_gate.nx source
↩ module page · 158 lines · 6372 B
1// nx_vizsla_notify_mcp_gate.nx -- GATE: the COMPOSING vizsla_notify tool runs over the tools/call
2// execution layer. Kept SEPARATE from nx_vizsla_mcp_gate (a fresh, minimal process) because notify is
3// a COMPOSING tool -- it forks the blessed remind engine -- so this proves the double-fork path
4// (grandparent tools/call -> notify -> remind) works over tea_run_argv, exactly as the live /mcp does.
5//
6// Rows: 1 notify-resolve 2 mcp-exec-email (RFC5322 + Date header) 3 mcp-exec-deliver (on-site)
7// Evidence: VIZSLA-NOTIFY-MCP-GATE -> stdout + knowledge/status/vizsla_gate.log; exit 0 iff 3/3.
8// expect_exit: 0 license_tier: ORIGINAL
9import "nx_tool_exec_allow.nx"
10import "nx_gate_verdict.nx"
11
12func mn_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
13func mn_p(s: *u8) -> i64 { sys_write(1, s, mn_slen(s)); return 0 }
14
15func mn_cat(dst: *u8, off: i64, s: *u8) -> i64 {
16 var i: i64 = 0
17 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
18 return off + i
19}
20
21func mn_catn(dst: *u8, off: i64, v: i64) -> i64 {
22 var o: i64 = off
23 var m: i64 = v
24 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m }
25 let t: *u8 = sys_mmap(28)
26 var k: i64 = 0
27 if m == 0 { t[0] = 48 as u8; k = 1 }
28 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
29 var i: i64 = 0
30 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 }
31 return o + k
32}
33
34func mn_memhas(b: *u8, sz: i64, needle: *u8) -> i64 {
35 let n: i64 = mn_slen(needle)
36 if sz < n { return 0 }
37 var i: i64 = 0
38 while i + n <= sz {
39 var ok: i64 = 1
40 var j: i64 = 0
41 while j < n { if b[i + j] != needle[j] { ok = 0; j = n } else { j = j + 1 } }
42 if ok == 1 { return 1 }
43 i = i + 1
44 }
45 return 0
46}
47
48func mn_filehas(path: *u8, needle: *u8) -> i64 {
49 let fd: i64 = sys_openat_rd(path)
50 if fd < 0 { return 0 }
51 let sz: i64 = sys_lseek(fd, 0, 2)
52 sys_lseek(fd, 0, 0)
53 let buf: *u8 = sys_mmap(sz + 64)
54 var got: i64 = 0
55 var n: i64 = 1
56 while n > 0 { n = sys_read(fd, (buf as i64 + got) as *u8, 65536); if n > 0 { got = got + n } }
57 sys_close(fd)
58 return mn_memhas(buf, got, needle)
59}
60
61func mn_row(name: *u8, pass: i64) -> i64 {
62 mn_p("ROW " as *u8)
63 mn_p(name)
64 if pass == 1 { mn_p(" PASS\n" as *u8) } else { mn_p(" FAIL\n" as *u8) }
65 return pass
66}
67
68func main(argc: i64, argv: *i64) -> i64 {
69 mn_p("=== VIZSLA NOTIFY MCP GATE: the composing notify tool runs over tools/call (double-fork) ===\n" as *u8)
70 let conf: *u8 = "tool_allowlist.conf" as *u8
71 let out: *u8 = sys_mmap(16384)
72 let outlen: *i64 = sys_mmap(16) as *i64
73 let rc: *i64 = sys_mmap(16) as *i64
74 var pass: i64 = 0
75 var r: i64 = 0
76
77 // row 1: resolve
78 let path: *u8 = sys_mmap(4096)
79 let code: i64 = tea_resolve_from(conf, "vizsla_notify" as *u8, 13, path, 4096)
80 r = 0
81 if code == TEA_OK { if mn_memhas(path, mn_slen(path), "_offc/nx_vizsla_notify.elf" as *u8) == 1 { r = 1 } }
82 pass = pass + mn_row("notify-resolve" as *u8, r)
83
84 // row 2: EXECUTE email over tools/call (FIRST fork -> notify forks remind = the double-fork path)
85 let av: *i64 = sys_mmap(8 * 12) as *i64
86 av[0] = 0
87 av[1] = "email" as *u8 as i64
88 av[2] = "knowledge/vizsla/demo/cal-" as *u8 as i64
89 av[3] = "knowledge/vizsla/demo/contacts.txt" as *u8 as i64
90 av[4] = "2026-07-08" as *u8 as i64
91 av[5] = "14,7,1,0" as *u8 as i64
92 av[6] = "vizsla@jasonewest.com" as *u8 as i64
93 av[7] = "you@example.com" as *u8 as i64
94 av[8] = "/tmp/vzmn_email.eml" as *u8 as i64
95 av[9] = 0
96 let x2: i64 = tea_run_argv_from(conf, "vizsla_notify" as *u8, 13, av, out, 16384, outlen, rc)
97 r = 0
98 if x2 == 0 { if rc[0] == TEA_OK {
99 if mn_memhas(out, outlen[0], "VIZSLA-NOTIFY-EMAIL today=2026-07-08" as *u8) == 1 {
100 if mn_filehas("/tmp/vzmn_email.eml" as *u8, "Date: Wed, 08 Jul 2026 09:00:00 +0000" as *u8) == 1 { r = 1 }
101 }
102 } }
103 pass = pass + mn_row("mcp-exec-email" as *u8, r)
104
105 // row 3: EXECUTE on-site deliver over tools/call
106 let pfx: *u8 = sys_mmap(128)
107 var po: i64 = 0
108 po = mn_cat(pfx, po, "/tmp/vzmnN" as *u8)
109 po = mn_catn(pfx, po, sys_now_us())
110 po = mn_cat(pfx, po, "-" as *u8)
111 pfx[po] = 0 as u8
112 let av3: *i64 = sys_mmap(8 * 12) as *i64
113 av3[0] = 0
114 av3[1] = "deliver" as *u8 as i64
115 av3[2] = pfx as i64
116 av3[3] = "knowledge/vizsla/demo/cal-" as *u8 as i64
117 av3[4] = "knowledge/vizsla/demo/contacts.txt" as *u8 as i64
118 av3[5] = "2026-07-08" as *u8 as i64
119 av3[6] = "0" as *u8 as i64
120 av3[7] = 0
121 let x3: i64 = tea_run_argv_from(conf, "vizsla_notify" as *u8, 13, av3, out, 16384, outlen, rc)
122 r = 0
123 if x3 == 0 { if rc[0] == TEA_OK {
124 if mn_memhas(out, outlen[0], "VIZSLA-NOTIFY-DELIVER channel=onsite today=2026-07-08" as *u8) == 1 { r = 1 }
125 } }
126 pass = pass + mn_row("mcp-exec-deliver" as *u8, r)
127
128 let permil: i64 = (pass * 1000) / 3
129 let logfd: i64 = sys_openat_append("knowledge/status/vizsla_gate.log" as *u8, 0x1a4)
130 var fdi: i64 = 0
131 while fdi < 2 {
132 var fd: i64 = 1
133 if fdi == 1 { fd = logfd }
134 if fd > 0 {
135 let line: *u8 = sys_mmap(256)
136 var o: i64 = 0
137 o = mn_cat(line, o, "VIZSLA-NOTIFY-MCP-GATE epoch=" as *u8)
138 o = mn_catn(line, o, sys_now_realtime_sec())
139 o = mn_cat(line, o, " rows=3 pass=" as *u8)
140 o = mn_catn(line, o, pass)
141 o = mn_cat(line, o, " permil=" as *u8)
142 o = mn_catn(line, o, permil)
143 if pass == 3 { o = mn_cat(line, o, " verdict=GREEN\n" as *u8) } else { o = mn_cat(line, o, " verdict=RED\n" as *u8) }
144 sys_write(fd, line, o)
145 }
146 fdi = fdi + 1
147 }
148 if logfd > 0 { sys_close(logfd) }
149 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
150 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
151 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
152 let ctr__dry: *i64 = gv_ctr()
153 ctr__dry[0] = pass
154 ctr__dry[1] = 3
155 let rc__dry: i64 = gv_verdict("VIZSLA-NOTIFY-MCP-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
156 sys_exit(rc__dry)
157 return rc__dry
158}