code wiki / _hdl_build / nx_vizsla_group_gate.nx
nx_vizsla_group_gate.nx source
↩ module page · 360 lines · 16170 B
1// nx_vizsla_group_gate.nx -- VIZSLA GROUP gate: multi-member find-a-time + slot poll proven
2// against HAND-COMPUTED intersection fixtures. GENUINE COMPOSITION: member calendars are loaded
3// through the real nx_vizsla_calendar organ (cal: + rec: records), then nx_vizsla_group reads
4// those member stores -- nothing is mocked.
5//
6// member calendars (2026-07-10):
7// alice: 09:00-10:00 planning, 13:00-14:00 lunch
8// bob: 10:00-12:00 work, RRULE gym DAILY x2 15:00-16:00 (07-10 + 07-11)
9// group busy union 07-10: [540,720) [780,840) [900,960)
10// free in [480,1320): [480,540) [720,780) [840,900) [960,1320)
11// findtime dur=60 07-10 -> 4 candidates (08:00,12:00,14:00,16:00 windows)
12// findtime dur=90 07-10..07-11 -> 3 (evening 07-10 + two 07-11 windows; short gaps drop)
13// 07-11 dur=60 -> free ends 15:00 where bob's RECURRING gym starts
14// poll p1: s1 2026-07-10 16:00+90, s2 2026-07-11 08:00+90
15// initial votes: all 4 yes -> tie 2-2 -> winner=s1 (lexicographic tie-break)
16// bob changes s1 to no LATER (2026-07-09) -> s1 1-1, s2 2 -> winner=s2 (latest-wins)
17//
18// Rows:
19// 1 fixture-cal-loads alice+bob calendars load via the real organ (new=2 each)
20// 2 group-load 9 records (3 MEMBER + 2 SLOT + 4 VOTE) new=9 seg-6303
21// 3 idempotent-reload new=0 dup_instore=9 (law 10)
22// 4 findtime-members g1 has members=2 (carol is g2 -- roster isolation) candidates=4
23// 5 findtime-morning-slot 08:00-09:00 fits=1
24// 6 findtime-gap-slot 12:00-13:00 fits=1 (between-meetings gap found)
25// 7 findtime-evening-slot 16:00-22:00 fits=6
26// 8 findtime-dur90 2 days dur=90 -> candidates=3 (short windows honestly dropped)
27// 9 recurring-blocks 07-11 free window ENDS 15:00 at bob's recurring gym
28// 10 tally-tie-lexicographic winner=s1 yes=2 on the 2-2 tie
29// 11 tally-latest-wins after bob's later no: s1 yes=1 no=1, winner=s2 yes=2
30// 12 findtime-determinism byte-identical twice
31// 13 loud-fail-unknown-gid findtime ghost -> exit 1 [negative control]
32// 14 loud-fail-bad-date findtime from=2026-13-99 -> exit 1 [negative control]
33// 15 loud-fail-bad-vote VOTE maybe -> load exit 1 [negative control]
34// Evidence: VIZSLA-GRP-GATE -> stdout + knowledge/status/vizsla_gate.log; exit 0 iff 15/15.
35// spec: knowledge/research/2026-06-22-vizsla-sclass-roadmap.md license_tier: ORIGINAL
36import "nx_syscalls.nx"
37import "nx_gate_verdict.nx"
38
39func gg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
40func gg_p(s: *u8) -> i64 { sys_write(1, s, gg_slen(s)); return 0 }
41
42func gg_cat(dst: *u8, off: i64, s: *u8) -> i64 {
43 var i: i64 = 0
44 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
45 return off + i
46}
47
48func gg_catn(dst: *u8, off: i64, v: i64) -> i64 {
49 var o: i64 = off
50 var m: i64 = v
51 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m }
52 let t: *u8 = sys_mmap(28)
53 var k: i64 = 0
54 if m == 0 { t[0] = 48 as u8; k = 1 }
55 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
56 var i: i64 = 0
57 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 }
58 return o + k
59}
60
61func gg_write(path: *u8, content: *u8) -> i64 {
62 let fd: i64 = sys_openat_wr(path, 0x1a4)
63 if fd < 0 { return 0 - 1 }
64 sys_write(fd, content, gg_slen(content))
65 sys_close(fd)
66 return 0
67}
68
69func gg_readall(path: *u8, szout: *i64) -> *u8 {
70 let fd: i64 = sys_openat_rd(path)
71 if fd < 0 { szout[0] = 0 - 1; return 0 as *u8 }
72 let sz: i64 = sys_lseek(fd, 0, 2)
73 sys_lseek(fd, 0, 0)
74 let buf: *u8 = sys_mmap(sz + 64)
75 var got: i64 = 0
76 var n: i64 = 1
77 while n > 0 {
78 n = sys_read(fd, (buf as i64 + got) as *u8, 65536)
79 if n > 0 { got = got + n }
80 }
81 sys_close(fd)
82 szout[0] = got
83 return buf
84}
85
86func gg_runv(elf: *u8, args: *i64, outpath: *u8) -> i64 {
87 let pid: i64 = sys_fork()
88 if pid == 0 {
89 if (outpath as i64) != 0 {
90 let ofd: i64 = sys_openat_wr(outpath, 0x1a4)
91 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) }
92 }
93 let argv: *i64 = sys_mmap(128) as *i64
94 argv[0] = elf as i64
95 var i: i64 = 0
96 var go: i64 = 1
97 while go == 1 {
98 if args[i] == 0 { go = 0 } else {
99 argv[i + 1] = args[i]
100 i = i + 1
101 }
102 }
103 argv[i + 1] = 0
104 let envp: *i64 = sys_mmap(16) as *i64
105 envp[0] = 0
106 sys_execve(elf, argv, envp)
107 sys_exit(127)
108 }
109 let st: *i64 = sys_mmap(16) as *i64
110 sys_wait4(pid, st, 0)
111 let sig: i64 = st[0] & 0x7f
112 if sig != 0 { return 128 + sig }
113 return (st[0] >> 8) & 0xff
114}
115
116func gg_has(path: *u8, needle: *u8) -> i64 {
117 let szp: *i64 = sys_mmap(16) as *i64
118 let b: *u8 = gg_readall(path, szp)
119 let sz: i64 = szp[0]
120 let n: i64 = gg_slen(needle)
121 if sz < n { return 0 }
122 var i: i64 = 0
123 while i + n <= sz {
124 var ok: i64 = 1
125 var j: i64 = 0
126 while j < n {
127 if b[i + j] != needle[j] { ok = 0; j = n } else { j = j + 1 }
128 }
129 if ok == 1 { return 1 }
130 i = i + 1
131 }
132 return 0
133}
134
135func gg_fileeq(p1: *u8, p2: *u8) -> i64 {
136 let s1: *i64 = sys_mmap(16) as *i64
137 let s2: *i64 = sys_mmap(16) as *i64
138 let b1: *u8 = gg_readall(p1, s1)
139 let b2: *u8 = gg_readall(p2, s2)
140 if s1[0] != s2[0] { return 0 }
141 if s1[0] <= 0 { return 0 }
142 var i: i64 = 0
143 while i < s1[0] {
144 if b1[i] != b2[i] { return 0 }
145 i = i + 1
146 }
147 return 1
148}
149
150func gg_row(name: *u8, pass: i64) -> i64 {
151 gg_p("ROW " as *u8)
152 gg_p(name)
153 if pass == 1 { gg_p(" PASS\n" as *u8) } else { gg_p(" FAIL\n" as *u8) }
154 return pass
155}
156
157func gg_args(a1: *u8, a2: *u8, a3: *u8, a4: *u8, a5: *u8, a6: *u8, a7: *u8, a8: *u8) -> *i64 {
158 let a: *i64 = sys_mmap(128) as *i64
159 a[0] = a1 as i64
160 a[1] = a2 as i64
161 a[2] = a3 as i64
162 a[3] = a4 as i64
163 a[4] = a5 as i64
164 a[5] = a6 as i64
165 a[6] = a7 as i64
166 a[7] = a8 as i64
167 a[8] = 0
168 return a
169}
170
171func gg_ensure(elf: *u8, organ: *u8) -> i64 {
172 let pr: i64 = sys_openat_rd(elf)
173 if pr >= 0 { sys_close(pr); return 0 }
174 gg_p(" instrument missing -> rebuilding via durable runner\n" as *u8)
175 gg_runv("_offc/nx_sov_build_run.elf" as *u8, gg_args(organ, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/vzg_rebuild.out" as *u8)
176 return 0
177}
178
179func gg_mkpfx(tag: *u8, us: i64) -> *u8 {
180 let pfx: *u8 = sys_mmap(128)
181 var po: i64 = 0
182 po = gg_cat(pfx, po, tag)
183 po = gg_catn(pfx, po, us)
184 po = gg_cat(pfx, po, "-" as *u8)
185 pfx[po] = 0 as u8
186 return pfx
187}
188
189func main(argc: i64, argv: *i64) -> i64 {
190 gg_p("=== VIZSLA GROUP GATE: find-a-time intersection + poll tally KATs (hand-computed) ===\n" as *u8)
191 let cal: *u8 = "buildroot/_build/nx_vizsla_calendar.sov.elf" as *u8
192 let grp: *u8 = "buildroot/_build/nx_vizsla_group.sov.elf" as *u8
193 gg_ensure(cal, "nx_vizsla_calendar" as *u8)
194 gg_ensure(grp, "nx_vizsla_group" as *u8)
195
196 let us: i64 = sys_now_us()
197 let pfxA: *u8 = gg_mkpfx("/tmp/vzgA" as *u8, us)
198 let pfxB: *u8 = gg_mkpfx("/tmp/vzgB" as *u8, us)
199 let pfxG: *u8 = gg_mkpfx("/tmp/vzgG" as *u8, us)
200
201 gg_write("/tmp/vzg_calA.txt" as *u8, "EVENT a1 2026-07-10 540 60 planning\nEVENT a2 2026-07-10 780 60 lunch\n" as *u8)
202 gg_write("/tmp/vzg_calB.txt" as *u8, "EVENT b1 2026-07-10 600 120 work\nRRULE gym 2026-07-10 900 60 DAILY 1 2 gym\n" as *u8)
203
204 var pass: i64 = 0
205 var r: i64 = 0
206
207 // row 1: member calendars load through the REAL calendar organ
208 let rcA: i64 = gg_runv(cal, gg_args("load" as *u8, "/tmp/vzg_calA.txt" as *u8, pfxA, "6301" as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/vzg_lA.txt" as *u8)
209 let rcB: i64 = gg_runv(cal, gg_args("load" as *u8, "/tmp/vzg_calB.txt" as *u8, pfxB, "6302" as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/vzg_lB.txt" as *u8)
210 r = 0
211 if rcA == 0 { if rcB == 0 {
212 if gg_has("/tmp/vzg_lA.txt" as *u8, "VIZSLA-CAL-LOAD scanned=2 new=2" as *u8) == 1 {
213 if gg_has("/tmp/vzg_lB.txt" as *u8, "VIZSLA-CAL-LOAD scanned=2 new=2" as *u8) == 1 { r = 1 }
214 }
215 } }
216 pass = pass + gg_row("fixture-cal-loads" as *u8, r)
217
218 // group file (dynamic: carries the real member cal prefixes)
219 let gf: *u8 = sys_mmap(4096)
220 var go2: i64 = 0
221 go2 = gg_cat(gf, go2, "MEMBER g1 alice " as *u8)
222 go2 = gg_cat(gf, go2, pfxA)
223 go2 = gg_cat(gf, go2, "\nMEMBER g1 bob " as *u8)
224 go2 = gg_cat(gf, go2, pfxB)
225 go2 = gg_cat(gf, go2, "\nMEMBER g2 carol /tmp/vzg-none-" as *u8)
226 go2 = gg_cat(gf, go2, "\nSLOT p1 s1 2026-07-10 960 90\nSLOT p1 s2 2026-07-11 480 90\nVOTE p1 s1 alice yes 2026-07-08\nVOTE p1 s1 bob yes 2026-07-08\nVOTE p1 s2 alice yes 2026-07-08\nVOTE p1 s2 bob yes 2026-07-08\n" as *u8)
227 gf[go2] = 0 as u8
228 gg_write("/tmp/vzg_grp.txt" as *u8, gf)
229
230 // row 2: group load
231 let rc2: i64 = gg_runv(grp, gg_args("load" as *u8, "/tmp/vzg_grp.txt" as *u8, pfxG, "6303" as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/vzg_r2.txt" as *u8)
232 r = 0
233 if rc2 == 0 {
234 if gg_has("/tmp/vzg_r2.txt" as *u8, "VIZSLA-GRP-LOAD scanned=9 new=9 dup_infile=0 dup_instore=0 segment=seg-6303" as *u8) == 1 { r = 1 }
235 }
236 pass = pass + gg_row("group-load" as *u8, r)
237
238 // row 3: idempotent reload
239 let rc3: i64 = gg_runv(grp, gg_args("load" as *u8, "/tmp/vzg_grp.txt" as *u8, pfxG, "6304" as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/vzg_r3.txt" as *u8)
240 r = 0
241 if rc3 == 0 {
242 if gg_has("/tmp/vzg_r3.txt" as *u8, "VIZSLA-GRP-LOAD scanned=9 new=0 dup_infile=0 dup_instore=9 segment=none" as *u8) == 1 { r = 1 }
243 }
244 pass = pass + gg_row("idempotent-reload" as *u8, r)
245
246 // row 4: findtime members + candidate count (dur=60, 07-10, bounds 08:00-22:00)
247 let rc4: i64 = gg_runv(grp, gg_args("findtime" as *u8, pfxG, "g1" as *u8, "2026-07-10" as *u8, "2026-07-10" as *u8, "60" as *u8, "480" as *u8, "1320" as *u8), "/tmp/vzg_r4.txt" as *u8)
248 r = 0
249 if rc4 == 0 {
250 if gg_has("/tmp/vzg_r4.txt" as *u8, "VIZSLA-GRP-FIND-VERDICT gid=g1 members=2 days=1 dur=60 candidates=4" as *u8) == 1 { r = 1 }
251 }
252 pass = pass + gg_row("findtime-members" as *u8, r)
253
254 // row 5: morning window before everything
255 r = gg_has("/tmp/vzg_r4.txt" as *u8, "VIZSLA-GRP-SLOT gid=g1 date=2026-07-10 start=08:00 end=09:00 fits=1" as *u8)
256 pass = pass + gg_row("findtime-morning-slot" as *u8, r)
257
258 // row 6: the between-meetings gap
259 r = gg_has("/tmp/vzg_r4.txt" as *u8, "VIZSLA-GRP-SLOT gid=g1 date=2026-07-10 start=12:00 end=13:00 fits=1" as *u8)
260 pass = pass + gg_row("findtime-gap-slot" as *u8, r)
261
262 // row 7: evening window
263 r = gg_has("/tmp/vzg_r4.txt" as *u8, "VIZSLA-GRP-SLOT gid=g1 date=2026-07-10 start=16:00 end=22:00 fits=6" as *u8)
264 pass = pass + gg_row("findtime-evening-slot" as *u8, r)
265
266 // row 8: dur=90 across two days -> short windows drop, candidates=3
267 let rc8: i64 = gg_runv(grp, gg_args("findtime" as *u8, pfxG, "g1" as *u8, "2026-07-10" as *u8, "2026-07-11" as *u8, "90" as *u8, "480" as *u8, "1320" as *u8), "/tmp/vzg_r8.txt" as *u8)
268 r = 0
269 if rc8 == 0 {
270 if gg_has("/tmp/vzg_r8.txt" as *u8, "VIZSLA-GRP-FIND-VERDICT gid=g1 members=2 days=2 dur=90 candidates=3" as *u8) == 1 { r = 1 }
271 }
272 pass = pass + gg_row("findtime-dur90" as *u8, r)
273
274 // row 9: recurring gym occurrence bounds the 07-11 free window at 15:00
275 let rc9: i64 = gg_runv(grp, gg_args("findtime" as *u8, pfxG, "g1" as *u8, "2026-07-11" as *u8, "2026-07-11" as *u8, "60" as *u8, "480" as *u8, "1320" as *u8), "/tmp/vzg_r9.txt" as *u8)
276 r = 0
277 if rc9 == 0 {
278 if gg_has("/tmp/vzg_r9.txt" as *u8, "VIZSLA-GRP-SLOT gid=g1 date=2026-07-11 start=08:00 end=15:00 fits=7" as *u8) == 1 { r = 1 }
279 }
280 pass = pass + gg_row("recurring-blocks" as *u8, r)
281
282 // row 10: tally on the 2-2 tie -> lexicographic winner s1
283 let rc10: i64 = gg_runv(grp, gg_args("tally" as *u8, pfxG, "p1" as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/vzg_r10.txt" as *u8)
284 r = 0
285 if rc10 == 0 {
286 if gg_has("/tmp/vzg_r10.txt" as *u8, "VIZSLA-GRP-TALLY-VERDICT pid=p1 slots=2 votes=4 winner=s1 yes=2" as *u8) == 1 { r = 1 }
287 }
288 pass = pass + gg_row("tally-tie-lexicographic" as *u8, r)
289
290 // row 11: bob flips s1 to no on a LATER date -> latest-wins, winner s2
291 gg_write("/tmp/vzg_chg.txt" as *u8, "VOTE p1 s1 bob no 2026-07-09\n" as *u8)
292 gg_runv(grp, gg_args("load" as *u8, "/tmp/vzg_chg.txt" as *u8, pfxG, "6305" as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/vzg_l11.txt" as *u8)
293 let rc11: i64 = gg_runv(grp, gg_args("tally" as *u8, pfxG, "p1" as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/vzg_r11.txt" as *u8)
294 r = 0
295 if rc11 == 0 {
296 if gg_has("/tmp/vzg_r11.txt" as *u8, "VIZSLA-GRP-TALLY pid=p1 slot=s1 date=2026-07-10 start=16:00 yes=1 no=1" as *u8) == 1 {
297 if gg_has("/tmp/vzg_r11.txt" as *u8, "VIZSLA-GRP-TALLY-VERDICT pid=p1 slots=2 votes=4 winner=s2 yes=2" as *u8) == 1 { r = 1 }
298 }
299 }
300 pass = pass + gg_row("tally-latest-wins" as *u8, r)
301
302 // row 12: determinism
303 gg_runv(grp, gg_args("findtime" as *u8, pfxG, "g1" as *u8, "2026-07-10" as *u8, "2026-07-10" as *u8, "60" as *u8, "480" as *u8, "1320" as *u8), "/tmp/vzg_r12.txt" as *u8)
304 r = gg_fileeq("/tmp/vzg_r4.txt" as *u8, "/tmp/vzg_r12.txt" as *u8)
305 pass = pass + gg_row("findtime-determinism" as *u8, r)
306
307 // row 13: unknown gid (negative control)
308 let rc13: i64 = gg_runv(grp, gg_args("findtime" as *u8, pfxG, "ghost" as *u8, "2026-07-10" as *u8, "2026-07-10" as *u8, "60" as *u8, "480" as *u8, "1320" as *u8), "/tmp/vzg_r13.txt" as *u8)
309 r = 0
310 if rc13 == 1 { r = 1 }
311 pass = pass + gg_row("loud-fail-unknown-gid" as *u8, r)
312
313 // row 14: bad date (negative control)
314 let rc14: i64 = gg_runv(grp, gg_args("findtime" as *u8, pfxG, "g1" as *u8, "2026-13-99" as *u8, "2026-07-10" as *u8, "60" as *u8, "480" as *u8, "1320" as *u8), "/tmp/vzg_r14.txt" as *u8)
315 r = 0
316 if rc14 == 1 { r = 1 }
317 pass = pass + gg_row("loud-fail-bad-date" as *u8, r)
318
319 // row 15: bad vote value (negative control)
320 gg_write("/tmp/vzg_badv.txt" as *u8, "VOTE p1 s1 alice maybe 2026-07-08\n" as *u8)
321 let rc15: i64 = gg_runv(grp, gg_args("load" as *u8, "/tmp/vzg_badv.txt" as *u8, pfxG, "6306" as *u8, 0 as *u8, 0 as *u8, 0 as *u8, 0 as *u8), "/tmp/vzg_r15.txt" as *u8)
322 r = 0
323 if rc15 == 1 { r = 1 }
324 pass = pass + gg_row("loud-fail-bad-vote" as *u8, r)
325
326 let permil: i64 = (pass * 1000) / 15
327 let logfd: i64 = sys_openat_append("knowledge/status/vizsla_gate.log" as *u8, 0x1a4)
328 var fdi: i64 = 0
329 while fdi < 2 {
330 var fd: i64 = 1
331 if fdi == 1 { fd = logfd }
332 if fd > 0 {
333 let line: *u8 = sys_mmap(256)
334 var o: i64 = 0
335 o = gg_cat(line, o, "VIZSLA-GRP-GATE epoch=" as *u8)
336 o = gg_catn(line, o, sys_now_realtime_sec())
337 o = gg_cat(line, o, " rows=15 pass=" as *u8)
338 o = gg_catn(line, o, pass)
339 o = gg_cat(line, o, " permil=" as *u8)
340 o = gg_catn(line, o, permil)
341 if pass == 15 {
342 o = gg_cat(line, o, " verdict=GREEN\n" as *u8)
343 } else {
344 o = gg_cat(line, o, " verdict=RED\n" as *u8)
345 }
346 sys_write(fd, line, o)
347 }
348 fdi = fdi + 1
349 }
350 if logfd > 0 { sys_close(logfd) }
351 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
352 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
353 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
354 let ctr__dry: *i64 = gv_ctr()
355 ctr__dry[0] = pass
356 ctr__dry[1] = 15
357 let rc__dry: i64 = gv_verdict("VIZSLA-GROUP-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
358 sys_exit(rc__dry)
359 return rc__dry
360}