nx_paced_fetch_gate.nx source
↩ module page · 118 lines · 6957 B
1// nx_paced_fetch_gate.nx -- GATE for nx_paced_fetch (the capture path's ONE pacing hook, /compare/mediaingest R0).
2// No network: both fixture hosts live under the reserved .invalid TLD and nothing is ever fetched. Every decision
3// under test is nx_crawl_pace's own arithmetic (its policy has its own gate); this gate proves the HOOK's
4// composition around it -- host extraction, the wait actually served on the clock, the burst form's discriminator,
5// the decay, and that a throttle on one host never touches another. The pace table is PERSISTENT and shared with
6// production, so both fixture hosts are reset at setup and teardown (the same self-isolation nx_crawl_pace_gate
7// established on 2026-08-02). license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_crawl_pace.nx"
10import "nx_paced_fetch.nx"
11import "nx_gate_verdict.nx"
12
13const G_URL_A: *u8 = "https://pf-fixture-a.invalid/seg/001.ts?tok=1"
14const G_URL_B: *u8 = "https://pf-fixture-b.invalid/x"
15const G_URL_HOSTPORT: *u8 = "https://cdn.example.invalid:8443/a/b?x=1#f"
16const G_URL_TINY: *u8 = "http://h/p"
17const G_URL_NOSCHEME: *u8 = "no-scheme-here/path"
18const G_HOST_LEN_CDN: i64 = 19 // strlen("cdn.example.invalid") -- the port and path must NOT be part of the host
19const G_HOST_LEN_TINY: i64 = 1
20const G_RA_S: i64 = 2 // the planted Retry-After: short enough to sleep through in a gate, long enough that a wait of 0 cannot pass by accident
21const G_MS: i64 = 1000
22const G_HTTP_429: i64 = 429
23const G_HTTP_200: i64 = 200
24const G_CLOCK_TOLERANCE_PERMIL: i64 = 900 // the sleep must serve at least 900 permil of the wait it reported (scheduler jitter on a loaded box, never a shortfall by design)
25const G_PERMIL: i64 = 1000
26const G_HOST_CAP: i64 = 512
27const G_SLOTS: i64 = 4096 // PACE_SLOTS -- the open-addressed pace table's fixed slot count (nx_crawl_pace)
28const G_REC: i64 = 40 // PACE_REC -- one pace record's byte width; hash is its first 8 bytes
29
30func main() -> i64 {
31 gv_head("=== nx_paced_fetch_gate -- the capture path's ONE pacing hook (mediaingest R0) ===" as *u8)
32 let c: *i64 = gv_ctr()
33 let h: *u8 = sys_mmap(G_HOST_CAP)
34
35 // ---- host extraction: the only new arithmetic this lib carries ----
36 let n1: i64 = pf_host_of(G_URL_HOSTPORT, h, G_HOST_CAP)
37 var h1ok: i64 = 0
38 if n1 == G_HOST_LEN_CDN { if h[n1] == (0 as u8) { h1ok = 1 } }
39 gv_check("host-of-scheme-host-port-path-query-fragment-stops-at-the-port" as *u8, h1ok, c)
40 let n2: i64 = pf_host_of(G_URL_TINY, h, G_HOST_CAP)
41 var h2ok: i64 = 0
42 if n2 == G_HOST_LEN_TINY { if h[0] == (104 as u8) { h2ok = 1 } }
43 gv_check("host-of-single-char-host" as *u8, h2ok, c)
44 let n3: i64 = pf_host_of(G_URL_NOSCHEME, h, G_HOST_CAP)
45 var h3ok: i64 = 0
46 if n3 == 0 { h3ok = 1 }
47 gv_check("neg-control-no-scheme-yields-no-host" as *u8, h3ok, c)
48 var refused: i64 = 0
49 if pf_before(G_URL_NOSCHEME) == PF_NO_HOST { refused = 1 }
50 gv_check("neg-control-hostless-url-is-refused-not-silently-unpaced" as *u8, refused, c)
51
52 // ---- setup: isolate both fixture hosts (the table is persistent and shared) ----
53 pf_reset(G_URL_A)
54 pf_reset(G_URL_B)
55 var fresh_nodefer: i64 = 0
56 if pf_should_defer(G_URL_A) == 0 { fresh_nodefer = 1 }
57 gv_check("fixture-reached-fresh-host-does-not-defer" as *u8, fresh_nodefer, c)
58 var fresh_wait0: i64 = 0
59 if pf_before(G_URL_A) == 0 { fresh_wait0 = 1 }
60 gv_check("fresh-host-waits-zero" as *u8, fresh_wait0, c)
61
62 // ---- REPORT the table's fill (a full open-addressed table with no eviction is the R15 fail-open this
63 // hook was found breaking on: nishihost's table had reached G_SLOTS/G_SLOTS real hosts). This is a
64 // REPORTED number, not a tooth -- the write-lands-anyway property is the tooth below, and it holds
65 // whether the table is full (eviction path) or has room (append path). ----
66 let dbg: *u8 = pace_load_tbl()
67 var occ: i64 = 0
68 var si: i64 = 0
69 while si < G_SLOTS { if pace_ld(dbg, si * G_REC) != 0 { occ = occ + 1 } si = si + 1 }
70 gv_puts(" REPORT pace table occupied=" as *u8); gv_num(occ); gv_puts(" of " as *u8); gv_num(G_SLOTS)
71 if occ == G_SLOTS { gv_puts(" (FULL -- pf_after below exercises the R15 eviction path)" as *u8) }
72 gv_puts("\n" as *u8)
73
74 // ---- a 429 with Retry-After: the pacer's delay is exactly RA x 1000 (arithmetic, not timing). This is the
75 // write-lands tooth: it FAILS if a full table drops the record (the pre-R15 fail-open). ----
76 let d: i64 = pf_after(G_URL_A, G_HTTP_429, G_RA_S)
77 var dok: i64 = 0
78 if d == G_RA_S * G_MS { dok = 1 }
79 gv_check("429-with-retry-after-delay-equals-ra-times-1000-ms-even-on-a-full-table" as *u8, dok, c)
80 var defer_now: i64 = 0
81 if pf_should_defer(G_URL_A) == 1 { defer_now = 1 }
82 gv_check("throttled-host-now-defers" as *u8, defer_now, c)
83
84 // ---- the burst form waits on a THROTTLED host, and the wait is served on the clock ----
85 let t0: i64 = sys_now_realtime_ms()
86 let wb: i64 = pf_before_burst(G_URL_A)
87 let t1: i64 = sys_now_realtime_ms()
88 var wbpos: i64 = 0
89 if wb > 0 { wbpos = 1 }
90 gv_check("burst-form-waits-on-throttled-host" as *u8, wbpos, c)
91 // both clock teeth are BOUND to wb > 0: a zero wait would satisfy either inequality vacuously, and a tooth
92 // that passes on the empty case is not a tooth (measured on this gate's first run: 11/14 with both green)
93 var served: i64 = 0
94 if wb > 0 { if (t1 - t0) * G_PERMIL >= wb * G_CLOCK_TOLERANCE_PERMIL { served = 1 } }
95 gv_check("burst-wait-actually-served-on-the-clock" as *u8, served, c)
96 var bounded: i64 = 0
97 if wb > 0 { if wb <= G_RA_S * G_MS { bounded = 1 } }
98 gv_check("burst-wait-bounded-by-the-planted-retry-after" as *u8, bounded, c)
99
100 // ---- neg-control: a throttle on A never touches B ----
101 var b_nodefer: i64 = 0
102 if pf_should_defer(G_URL_B) == 0 { b_nodefer = 1 }
103 gv_check("neg-control-other-host-does-not-defer" as *u8, b_nodefer, c)
104 var b_wait0: i64 = 0
105 if pf_before_burst(G_URL_B) == 0 { b_wait0 = 1 }
106 gv_check("neg-control-other-host-burst-waits-zero" as *u8, b_wait0, c)
107
108 // ---- decay on success: one 200 takes one halving off; consec 1 -> 0 -> no longer defers ----
109 pf_after(G_URL_A, G_HTTP_200, 0)
110 var decayed: i64 = 0
111 if pf_should_defer(G_URL_A) == 0 { decayed = 1 }
112 gv_check("success-decays-throttle-and-the-host-stops-deferring" as *u8, decayed, c)
113
114 // ---- teardown: leave no fixture history in the production table ----
115 pf_reset(G_URL_A)
116 pf_reset(G_URL_B)
117 return gv_verdict("nx_paced_fetch_gate" as *u8, c, "every decision is nx_crawl_pace's own arithmetic (its policy gate owns those teeth); this gate proves only the hook's composition around it, on .invalid fixture hosts with no network, and resets both hosts before and after so the production pace table keeps no fixture history" as *u8)
118}