code wiki / _hdl_build / nx_apistack_ratelimit_gate.nx
nx_apistack_ratelimit_gate.nx source
↩ module page · 61 lines · 4067 B
1import "nx_gate_gn.nx"
2// nx_apistack_ratelimit_gate.nx -- hermetic gate for CAP-API-RATELIMIT. Proves the deterministic token-bucket:
3// burst up to cap then 429, refill over elapsed time, cap ceiling on long idle, data-driven per-key quotas, and
4// bit-identical decisions for identical inputs. Sovereign: nx_syscalls + nx_apistack_ratelimit. expect_exit: 0
5import "nx_syscalls.nx"
6import "nx_apistack_ratelimit.nx"
7import "nx_gate_verdict.nx"
8
9func gp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
10func g_app(buf: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){buf[off+i]=s[i];i=i+1} return off+i }
11
12func main(argc: i64, argv: *i64) -> i64 {
13 gp("=== nx_apistack_ratelimit_gate (deterministic token-bucket 429) ===\n" as *u8)
14 var pass: i64 = 0; var fail: i64 = 0
15
16 // T1 burst up to cap=5 (rate 1/s), then 429
17 let st: *i64 = sys_mmap(16) as *i64; st[0]=5; st[1]=100
18 var allows: i64 = 0; var i: i64 = 0
19 while i < 5 { allows = allows + rl_step(st, 5, 1, 100, 1); i = i + 1 }
20 let deny6: i64 = rl_step(st, 5, 1, 100, 1)
21 if allows == 5 { if deny6 == 0 { pass=pass+1; gp(" T1 burst 5 allow then 429 PASS\n" as *u8) } else { fail=fail+1; gp(" T1 FAIL 6th not denied\n" as *u8) } } else { fail=fail+1; gp(" T1 FAIL allows=" as *u8); gn(allows); gp("\n" as *u8) }
22
23 // T2 refill over 3s -> allow again
24 let a2: i64 = rl_step(st, 5, 1, 103, 1)
25 if a2 == 1 { pass=pass+1; gp(" T2 refill after 3s -> allow PASS\n" as *u8) } else { fail=fail+1; gp(" T2 FAIL\n" as *u8) }
26
27 // T3 long idle -> tokens cap at cap (not unbounded): after now=1000, exactly cap available
28 let st3: *i64 = sys_mmap(16) as *i64; st3[0]=0; st3[1]=100
29 rl_step(st3, 5, 1, 1000, 0) // cost 0 just refills; elapsed 900*1 capped at 5
30 if st3[0] == 5 { pass=pass+1; gp(" T3 long-idle refill CAPPED at cap=5 PASS\n" as *u8) } else { fail=fail+1; gp(" T3 FAIL tokens=" as *u8); gn(st3[0]); gp("\n" as *u8) }
31
32 // T4 data-driven quota from ratelimits.conf
33 let conf: *u8 = sys_mmap(128); var cn: i64 = 0
34 cn = g_app(conf, cn, "shard-a" as *u8); conf[cn]=9 as u8; cn=cn+1
35 cn = g_app(conf, cn, "10" as *u8); conf[cn]=9 as u8; cn=cn+1
36 cn = g_app(conf, cn, "2" as *u8); conf[cn]=10 as u8; cn=cn+1
37 let cfd: i64 = sys_openat_wr("/tmp/rl_conf.tsv" as *u8, 0x1a4); if cfd >= 0 { sys_write(cfd, conf, cn); sys_close(cfd) }
38 let ocap: *i64 = sys_mmap(8) as *i64; let orate: *i64 = sys_mmap(8) as *i64
39 let found: i64 = rl_config("/tmp/rl_conf.tsv" as *u8, "shard-a" as *u8, 7, ocap, orate)
40 if found == 1 { if ocap[0]==10 { if orate[0]==2 { pass=pass+1; gp(" T4 quota-as-data (shard-a cap=10 rate=2) PASS\n" as *u8) } } }
41 if found != 1 { fail=fail+1; gp(" T4 FAIL config miss\n" as *u8) }
42 else { if ocap[0]!=10 { fail=fail+1; gp(" T4 FAIL cap=" as *u8); gn(ocap[0]); gp("\n" as *u8) } }
43
44 // T5 determinism: identical inputs -> identical decision
45 let sa: *i64 = sys_mmap(16) as *i64; sa[0]=3; sa[1]=50
46 let sb: *i64 = sys_mmap(16) as *i64; sb[0]=3; sb[1]=50
47 let da: i64 = rl_step(sa, 5, 1, 52, 4)
48 let db: i64 = rl_step(sb, 5, 1, 52, 4)
49 if da == db { if sa[0]==sb[0] { pass=pass+1; gp(" T5 identical inputs -> identical decision + state PASS\n" as *u8) } else { fail=fail+1; gp(" T5 FAIL state diverged\n" as *u8) } } else { fail=fail+1; gp(" T5 FAIL decision diverged\n" as *u8) }
50
51 gp("RESULT pass=" as *u8); gn(pass); gp(" fail=" as *u8); gn(fail)
52 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
53 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
54 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
55 let ctr__dry: *i64 = gv_ctr()
56 ctr__dry[0] = pass
57 ctr__dry[1] = pass + fail
58 let rc__dry: i64 = gv_verdict("APISTACK-RATELIMIT-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
59 sys_exit(rc__dry)
60 return rc__dry
61}