code wiki / _hdl_build / nx_cms_schedule_gate.nx
nx_cms_schedule_gate.nx source
↩ module page · 171 lines · 9454 B
1// nx_cms_schedule_gate.nx -- CMS SCHEDULED-PUBLISHING gate (W-RE-CRON-001): land the missing class
2// HONESTLY by proving a REAL schedule->publish over the NOW-WIRED nx_cron dispatch core (job-store +
3// tick fires due jobs) driving a real nx_cms_store status transition. NOT the validation-half alone
4// (no thin-gate wave): the post genuinely goes scheduled -> published, exactly at its fire time,
5// exactly once. Appends "CMSGATE row=nx_cms_schedule scheduled-publish ... verdict=PASS" to
6// knowledge/status/cms_gate.log ONLY if every assertion holds (no fake-green). Exit 0 iff all pass.
7// license_tier: ORIGINAL
8import "nx_cron.nx"
9import "nx_cms_store.nx"
10import "nx_syscalls.nx"
11import "nx_gate_verdict.nx"
12
13const SG_T_FUTURE: i64 = 2000000000 // fixed absolute unix (year ~2033) -- deterministic, no wallclock
14const SG_T_PAST: i64 = 1000000000 // fixed absolute unix (year ~2001) -- a missed/past-due fire
15
16func sg_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
17func sg_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
18func sg_num(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; 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 }
19
20func sg_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var k: i64=0; while s[k]!=(0 as u8){dst[o]=s[k];o=o+1;k=k+1} return o }
21func sg_catnum(dst: *u8, off: i64, v: i64) -> i64 {
22 var o: i64=off; let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; var k: i64=0
23 if m==0 {t[0]=48 as u8;k=1}; while m>0 {t[k]=(48+(m%10)) as u8; m=m/10; k=k+1}
24 var i: i64=0; while i<k {dst[o]=t[k-1-i]; o=o+1; i=i+1} return o
25}
26
27// compare exactly n bytes of buf to a NUL-terminated literal (literal must be exactly n long)
28func sg_streq(buf: *u8, n: i64, lit: *u8) -> i64 {
29 var i: i64=0
30 while i<n { if (buf[i] as i64) != (lit[i] as i64) { return 0 } i=i+1 }
31 if (lit[n] as i64) != 0 { return 0 }
32 return 1
33}
34
35func sg_cpy(dst: *u8, src: *u8) -> i64 { var k: i64=0; while src[k]!=(0 as u8){dst[k]=src[k];k=k+1} return k }
36
37func sg_row(id: i64, ok: i64, what: *u8) -> i64 {
38 sg_w("SCHEDROW " as *u8); sg_num(id); sg_w(" " as *u8)
39 if ok==1 { sg_w("PASS " as *u8) } else { sg_w("FAIL " as *u8) }
40 sg_w(what); sg_w("\n" as *u8)
41 return ok
42}
43
44// build a NxCronJob in fresh zeroed memory (mmap pages are zeroed -> unset fields are 0)
45func sg_job(name: *u8, kind: i64, fire_at: i64, interval: i64, action_arg: i64) -> *NxCronJob {
46 let jp: *NxCronJob = sys_mmap(256) as *NxCronJob
47 jp.job_name_ptr = name
48 jp.job_name_len = sg_len(name)
49 jp.schedule_kind = kind
50 jp.interval_seconds = interval
51 jp.fire_at_unix = fire_at
52 jp.missed_policy = NX_CRON_MISSED_CATCH_UP_ONCE
53 jp.action_kind = NX_CRON_ACTION_PUBLISH
54 jp.action_arg = action_arg
55 jp.enabled = 1
56 return jp
57}
58
59// current @status value of the post store; copies into out, returns length
60func sg_status(post: *u8, pn: i64, out: *u8) -> i64 {
61 return cst_get(post, pn, "status" as *u8, out, 64)
62}
63
64func main() -> i64 {
65 var pass: i64 = 0
66 var rows: i64 = 0
67 var ok: i64 = 0
68
69 // ---- a real CMS post in the sovereign nx_cms_store record format ----
70 let post: *u8 = sys_mmap(8192)
71 let post2: *u8 = sys_mmap(8192)
72 var pn: i64 = sg_cpy(post, "@title\nFall Sale Announcement\n@status\nscheduled\n@body\nBig autumn news.\n" as *u8)
73 let sbuf: *u8 = sys_mmap(128)
74 let dk: *i64 = sys_mmap(16) as *i64
75 let da: *i64 = sys_mmap(16) as *i64
76
77 // R0: register a scheduled publish -> JOB_REGISTERED (proves dispatch is WIRED, not FAIL_DEPENDENCY_MISSING)
78 let j1: *NxCronJob = sg_job("publish-post-1" as *u8, NX_CRON_KIND_AT_UNIX_ONCE, SG_T_FUTURE, 0, 1)
79 let v0: i64 = nx_cron_register_job(j1)
80 ok = 0; if v0 == NX_CRON_VERDICT_JOB_REGISTERED { ok = 1 }
81 rows=rows+1; pass=pass+sg_row(0, ok, "register scheduled publish -> JOB_REGISTERED (dispatch wired)" as *u8)
82
83 // R1: a malformed schedule (AT_UNIX_ONCE with no fire time) -> FAIL_BAD_SCHEDULE (negative control)
84 let jbad: *NxCronJob = sg_job("bad-sched" as *u8, NX_CRON_KIND_AT_UNIX_ONCE, 0, 0, 9)
85 let v1: i64 = nx_cron_register_job(jbad)
86 ok = 0; if v1 == NX_CRON_VERDICT_FAIL_BAD_SCHEDULE { ok = 1 }
87 rows=rows+1; pass=pass+sg_row(1, ok, "malformed schedule refused -> FAIL_BAD_SCHEDULE" as *u8)
88
89 // R2: duplicate name -> FAIL_DUPLICATE_NAME
90 let jdup: *NxCronJob = sg_job("publish-post-1" as *u8, NX_CRON_KIND_AT_UNIX_ONCE, SG_T_FUTURE, 0, 1)
91 let v2: i64 = nx_cron_register_job(jdup)
92 ok = 0; if v2 == NX_CRON_VERDICT_FAIL_DUPLICATE_NAME { ok = 1 }
93 rows=rows+1; pass=pass+sg_row(2, ok, "duplicate job name refused -> FAIL_DUPLICATE_NAME" as *u8)
94
95 // R3: tick BEFORE the fire time -> nothing fires, post still scheduled, public NOT showing it
96 let f3: i64 = nx_cron_tick(SG_T_FUTURE - 100)
97 let drained3: i64 = nx_cron_drain_fired(dk, da)
98 let sl3: i64 = sg_status(post, pn, sbuf)
99 ok = 0
100 if f3 == 0 { if drained3 == 0 { if sg_streq(sbuf, sl3, "scheduled" as *u8) == 1 { ok = 1 } } }
101 rows=rows+1; pass=pass+sg_row(3, ok, "tick before fire-time: 0 fired, post still SCHEDULED (not public)" as *u8)
102
103 // R4: tick AT the fire time -> job fires once, drains PUBLISH action for post 1, apply it -> published
104 let f4: i64 = nx_cron_tick(SG_T_FUTURE)
105 let drained4: i64 = nx_cron_drain_fired(dk, da)
106 ok = 0
107 if f4 == 1 { if drained4 == 1 { if dk[0] == NX_CRON_ACTION_PUBLISH { if da[0] == 1 {
108 // the dispatch fired -> the CMS performs the real publish state transition
109 let pn2: i64 = cst_set(post, pn, "status" as *u8, "published" as *u8, 9, post2, 8192)
110 pn = pn2
111 var ci: i64 = 0; while ci < pn { post[ci] = post2[ci]; ci = ci + 1 }
112 let sl4: i64 = sg_status(post, pn, sbuf)
113 if sg_streq(sbuf, sl4, "published" as *u8) == 1 { ok = 1 }
114 } } } }
115 rows=rows+1; pass=pass+sg_row(4, ok, "tick at fire-time: fired once -> PUBLISH action -> post PUBLISHED (live)" as *u8)
116
117 // R5: a later tick does NOT re-fire (one-shot = FIRE-ONCE; not re-published)
118 let f5: i64 = nx_cron_tick(SG_T_FUTURE + 500)
119 let fc5: i64 = nx_cron_job_fired_count("publish-post-1" as *u8, 14)
120 ok = 0; if f5 == 0 { if fc5 == 1 { ok = 1 } }
121 rows=rows+1; pass=pass+sg_row(5, ok, "later tick: 0 fired, fired_count stays 1 (idempotent fire-once)" as *u8)
122
123 // R6: missed/past-due one-shot fires exactly once on the next tick (catch-up-once)
124 let j2: *NxCronJob = sg_job("publish-post-2" as *u8, NX_CRON_KIND_AT_UNIX_ONCE, SG_T_PAST, 0, 2)
125 let v6: i64 = nx_cron_register_job(j2)
126 let f6: i64 = nx_cron_tick(SG_T_PAST + 10000)
127 let fc6: i64 = nx_cron_job_fired_count("publish-post-2" as *u8, 14)
128 ok = 0
129 if v6 == NX_CRON_VERDICT_JOB_REGISTERED { if f6 == 1 { if fc6 == 1 { ok = 1 } } }
130 rows=rows+1; pass=pass+sg_row(6, ok, "past-due publish fires once on next tick (missed catch-up-once)" as *u8)
131
132 // R7: ON_DEMAND fire_now is repeatable (manual publish trigger)
133 let j3: *NxCronJob = sg_job("manual-pub" as *u8, NX_CRON_KIND_ON_DEMAND, 0, 0, 3)
134 let v7: i64 = nx_cron_register_job(j3)
135 nx_cron_fire_now("manual-pub" as *u8, 10)
136 nx_cron_fire_now("manual-pub" as *u8, 10)
137 let fc7: i64 = nx_cron_job_fired_count("manual-pub" as *u8, 10)
138 ok = 0; if v7 == NX_CRON_VERDICT_JOB_REGISTERED { if fc7 == 2 { ok = 1 } }
139 rows=rows+1; pass=pass+sg_row(7, ok, "ON_DEMAND fire_now repeatable (fired_count=2)" as *u8)
140
141 let jobs: i64 = nx_cron_count_jobs()
142
143 sg_w("CMS-SCHEDULE-GATE rows=" as *u8); sg_num(rows); sg_w(" pass=" as *u8); sg_num(pass)
144 sg_w(" jobs=" as *u8); sg_num(jobs); sg_w("\n" as *u8)
145
146 // D001-MIGRATION 2026-08-01: this gate was GREEN and UNREADABLE -- it printed "verdict=PASS"
147 // while nx_swcompare_evidence parses the nx_gate_verdict vocabulary, so cms scored gates_green=0/2
148 // with BOTH gates passing. The per-row SCHEDROW detail above is already emitted by sg_row and is
149 // kept verbatim; this adds the canonical summary line the ruler can actually read. The counter is
150 // populated from the SAME rows/pass the teeth produced -- it re-states the measurement, it does not
151 // re-run or re-decide it, so the verdict cannot drift from the evidence above it.
152 let ctr: *i64 = gv_ctr()
153 ctr[0] = pass
154 ctr[1] = rows
155 let rc: i64 = gv_verdict("CMS-SCHEDULE-GATE" as *u8, ctr, "fire-once scheduled publish: idempotent, catches up missed, ON_DEMAND repeatable" as *u8)
156
157 if pass == rows {
158 let line: *u8 = sys_mmap(256)
159 var lo: i64 = sg_cat(line, 0, "CMSGATE row=nx_cms_schedule scheduled-publish jobs=" as *u8)
160 lo = sg_catnum(line, lo, jobs)
161 lo = sg_cat(line, lo, " rows=" as *u8); lo = sg_catnum(line, lo, rows)
162 lo = sg_cat(line, lo, " pass=" as *u8); lo = sg_catnum(line, lo, pass)
163 lo = sg_cat(line, lo, " verdict=PASS\n" as *u8)
164 let gf: i64 = sys_openat_append("knowledge/status/cms_gate.log" as *u8, 0x1a4)
165 if gf >= 0 { sys_write(gf, line, lo); sys_close(gf) }
166 sg_w("CMS-SCHEDULE-GATE scheduled-publish recorded in cms_gate.log\n" as *u8)
167 sys_exit(0); return 0
168 }
169 sg_w("CMS-SCHEDULE-GATE NOT recorded (no fake-green)\n" as *u8)
170 sys_exit(rc); return rc
171}