code wiki / _hdl_build / nx_wflow_approve_gate.nx
nx_wflow_approve_gate.nx source
↩ module page · 123 lines · 6405 B
1// nx_wflow_approve_gate.nx -- R4 HUMAN-IN-THE-LOOP approvals gate. An `approve` step PARKS the run
2// (status=WAIT, in-flight -- neither DONE nor FAILED) until an operator decision record lands on the
3// SAME event-sourced ledger (WFDEC ... decision=APPROVED|DENIED by=<who>); wf_resume then completes or
4// fail-stops it. DENY WINS (fail-safe). Proves: parked run gates later steps; resume WITHOUT a decision
5// never fabricates approval; APPROVED unlocks exactly once (no re-exec of OK steps); DENIED fail-stops;
6// bad decision word refused; a decision for the WRONG step does NOT unlock. ZERO forks.
7// license_tier: ORIGINAL expect_exit: 0
8import "nx_wflow_engine.nx"
9import "nx_gate_verdict.nx"
10
11func ag_n(led: *u8, ridtok: *u8, st: *u8) -> i64 {
12 let szp: *i64 = sys_mmap(8) as *i64
13 let b: *u8 = wf_readall(led, szp)
14 if (b as i64) == 0 { return 0 - 1 }
15 return wf_lines_with2(b, szp[0], ridtok, st)
16}
17
18func main() -> i64 {
19 p("=== NX-WFLOW-APPROVE GATE (R4: approve step parks the run until an operator decision; deny wins) ===\n" as *u8)
20 var ok: i64 = 1
21 let now: i64 = wf_now()
22 let led: *u8 = sys_mmap(128)
23 var lo: i64 = 0
24 lo = wf_cat(led, lo, "/tmp/wfappr_" as *u8)
25 lo = wf_catn(led, lo, now)
26 lo = wf_cat(led, lo, ".log" as *u8)
27 led[lo] = 0 as u8
28
29 let flows: *i64 = sys_mmap(8 * 4) as *i64
30 flows[0] = "fa1|expense|-" as *u8 as i64
31 let steps: *i64 = sys_mmap(8 * 8) as *i64
32 steps[0] = "fa1|1|create-task|-|File the venue request|1" as *u8 as i64
33 steps[1] = "fa1|2|approve|operator|Spend 500 on the pavilion hire|1" as *u8 as i64
34 steps[2] = "fa1|3|notify|-|Approved and booked|1" as *u8 as i64
35
36 let cx: *i64 = sys_mmap(32) as *i64
37 cx[0] = steps as i64
38 cx[1] = 3
39 cx[2] = led as i64
40 cx[3] = 0
41
42 // T0 load validation: approve needs an approver name
43 let l0: i64 = wf_load(steps, 3)
44 let bad: *i64 = sys_mmap(16) as *i64
45 bad[0] = "f9|1|approve|-|x|1" as *u8 as i64
46 let lb: i64 = wf_load(bad, 1)
47 p(" T0 load=" as *u8); pn(l0); p(" badNoApprover=" as *u8); pn(lb); p("\n" as *u8)
48 if l0 != 3 { ok = 0 }
49 if lb != (0 - 1) { ok = 0 }
50
51 // T1 fire -> step1 OK, step2 PARKS (WAIT), step3 gated, run in-flight (no DONE, no FAILED)
52 let r1: i64 = wf_fire(cx, flows, 1, "expense~amount=500~id=A1" as *u8)
53 let w1: i64 = ag_n(led, " rid=A1.fa1 " as *u8, "status=WAIT" as *u8)
54 let d1: i64 = ag_n(led, " rid=A1.fa1 " as *u8, "status=DONE" as *u8)
55 let f1: i64 = ag_n(led, " rid=A1.fa1 " as *u8, "status=FAILED" as *u8)
56 let s3a: i64 = ag_n(led, " rid=A1.fa1 " as *u8, " step=3 " as *u8)
57 p(" T1 parked started=" as *u8); pn(r1); p(" wait=" as *u8); pn(w1); p(" done=" as *u8); pn(d1); p(" failed=" as *u8); pn(f1); p(" step3lines=" as *u8); pn(s3a); p("\n" as *u8)
58 if r1 != 1 { ok = 0 }
59 if w1 != 1 { ok = 0 }
60 if d1 != 0 { ok = 0 }
61 if f1 != 0 { ok = 0 }
62 if s3a != 0 { ok = 0 }
63
64 // T2 resume WITHOUT a decision -> still parked, approval is never fabricated
65 let rs1: i64 = wf_resume(cx)
66 let w2: i64 = ag_n(led, " rid=A1.fa1 " as *u8, "status=WAIT" as *u8)
67 let d2: i64 = ag_n(led, " rid=A1.fa1 " as *u8, "status=DONE" as *u8)
68 let s3b: i64 = ag_n(led, " rid=A1.fa1 " as *u8, " step=3 " as *u8)
69 p(" T2 nofabricate resumed=" as *u8); pn(rs1); p(" wait=" as *u8); pn(w2); p(" done=" as *u8); pn(d2); p(" step3lines=" as *u8); pn(s3b); p("\n" as *u8)
70 if rs1 != 1 { ok = 0 }
71 if w2 != 2 { ok = 0 }
72 if d2 != 0 { ok = 0 }
73 if s3b != 0 { ok = 0 }
74
75 // T3 APPROVED -> resume completes step2 + step3, DONE; step1 never re-executed
76 let dc: i64 = wf_decide(cx, "A1.fa1" as *u8, 2, "APPROVED" as *u8, "operator" as *u8)
77 let rs2: i64 = wf_resume(cx)
78 let d3: i64 = ag_n(led, " rid=A1.fa1 " as *u8, "status=DONE" as *u8)
79 let s1once: i64 = ag_n(led, " rid=A1.fa1 " as *u8, " step=1 status=OK" as *u8)
80 let s3c: i64 = ag_n(led, " rid=A1.fa1 " as *u8, " step=3 status=OK" as *u8)
81 p(" T3 approved deciderc=" as *u8); pn(dc); p(" resumed=" as *u8); pn(rs2); p(" done=" as *u8); pn(d3); p(" step1okOnce=" as *u8); pn(s1once); p(" step3ok=" as *u8); pn(s3c); p("\n" as *u8)
82 if dc != 0 { ok = 0 }
83 if rs2 != 1 { ok = 0 }
84 if d3 != 1 { ok = 0 }
85 if s1once != 1 { ok = 0 }
86 if s3c != 1 { ok = 0 }
87
88 // T4 DENIED path -> run fail-stops at the approval, step3 never runs
89 wf_fire(cx, flows, 1, "expense~amount=9000~id=A2" as *u8)
90 wf_decide(cx, "A2.fa1" as *u8, 2, "DENIED" as *u8, "operator" as *u8)
91 let rs3: i64 = wf_resume(cx)
92 let f4: i64 = ag_n(led, " rid=A2.fa1 " as *u8, "status=FAILED" as *u8)
93 let d4: i64 = ag_n(led, " rid=A2.fa1 " as *u8, "status=DONE" as *u8)
94 let s3d: i64 = ag_n(led, " rid=A2.fa1 " as *u8, " step=3 status=OK" as *u8)
95 p(" T4 denied resumed=" as *u8); pn(rs3); p(" failed=" as *u8); pn(f4); p(" done=" as *u8); pn(d4); p(" step3ok=" as *u8); pn(s3d); p("\n" as *u8)
96 if rs3 != 1 { ok = 0 }
97 if f4 != 1 { ok = 0 }
98 if d4 != 0 { ok = 0 }
99 if s3d != 0 { ok = 0 }
100
101 // T5 negctl precision: bad decision word refused; a decision for the WRONG step does not unlock
102 let db: i64 = wf_decide(cx, "A3.fa1" as *u8, 2, "MAYBE" as *u8, "operator" as *u8)
103 wf_fire(cx, flows, 1, "expense~amount=42~id=A3" as *u8)
104 wf_decide(cx, "A3.fa1" as *u8, 1, "APPROVED" as *u8, "operator" as *u8)
105 let rs4: i64 = wf_resume(cx)
106 let d5: i64 = ag_n(led, " rid=A3.fa1 " as *u8, "status=DONE" as *u8)
107 let w5: i64 = ag_n(led, " rid=A3.fa1 " as *u8, "status=WAIT" as *u8)
108 p(" T5 negctl badword=" as *u8); pn(db); p(" resumed=" as *u8); pn(rs4); p(" done=" as *u8); pn(d5); p(" waits=" as *u8); pn(w5); p("\n" as *u8)
109 if db != (0 - 1) { ok = 0 }
110 if d5 != 0 { ok = 0 }
111 if w5 < 2 { ok = 0 }
112
113 p("NX-WFLOW-APPROVE-GATE ledger=" as *u8); p(led); p(" forks=0 " as *u8)
114 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
115 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
116 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
117 let ctr__dry: *i64 = gv_ctr()
118 ctr__dry[0] = ok
119 ctr__dry[1] = 1
120 let rc__dry: i64 = gv_verdict("WFLOW-APPROVE-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
121 sys_exit(rc__dry)
122 return rc__dry
123}