nx_wflow_engine.nx source
↩ module page · 1566 lines · 74612 B
1// nx_wflow_engine.nx -- R1 KEYSTONE of the Workflow Automation ladder (/compare/automation census row
2// "Unified multi-step durable workflow engine"). A WORKFLOW = DATA (law: data as data):
3// flow header = `flowid|event-kind|cond(k=v or -)`
4// step row = `flowid|idx|action|channel-or-dash|arg|max-attempts` (idx 1-based, author ascending)
5// An EVENT = `kind~k=v~...~id=EN` (the nx_crm_flow contract). The engine upgrades nx_crm_flow from
6// single-shot in-memory rules to DURABLE EXECUTION (the Temporal model, sovereign):
7// - every transition is ONE appended line in an append-only RUN LEDGER (flock + O_APPEND, torn-free):
8// `WFRUN rid=<evid>.<flowid> flow=<f> step=<n> status=START|ATT|OK|FAILSTEP|FAILED|DONE att=<k>`
9// - state is DERIVED BY REPLAY of the ledger (event-sourced; nothing mutates, history is sacred)
10// - IDEMPOTENT by construction: a (event id, flow) pair with a START line never starts twice --
11// the dedup survives crashes/restarts (stronger than the in-memory seen[] of nx_crm_flow)
12// - per-step RETRY up to max-attempts; exhausted -> run FAILED at that step, later steps never run
13// - wf_resume: any run with START but neither DONE nor FAILED (a crash mid-flight) is completed
14// from its first non-OK step; already-OK steps are NOT re-executed (proven in the selftest)
15// ACTIONS v1 (in-process, composing the send plane like nx_crm_flow): create-task | send (any nx_send
16// channel, sr_valid-checked at load) | notify | update-field | probe-fail (DIAGNOSTIC: fails while
17// attempt < arg -- the deterministic retry witness; keep it out of production flows).
18// wf_load validates the WHOLE step set up front (unknown action / bad channel / attempts<1 = LOUD -1,
19// nothing half-loaded). Production ledger path suggestion: knowledge/status/wflow_runs.log.
20// R3 CONNECTOR LAYER (exec-organ): a step can run ANY blessed organ as a workflow step, resolved through a
21// GREEN fail-closed catalog (rows `name<TAB>elf<TAB>GREEN`, the tool_allowlist.conf shape; cx[3]=catalog path,
22// 0=exec-organ refused). Child exit 0 = step OK, nonzero = step fail (so per-step retry applies to REAL organ
23// execution). Production catalog: knowledge/wflow/connectors.conf.
24// PURE CORE (no main -- the house pure-core+gate idiom): gates = _hdl_build/nx_wflow_engine_gate (wf_selftest)
25// + _hdl_build/nx_wflow_connect_gate (connector battery). license_tier: ORIGINAL
26import "nx_send.nx"
27const WF_MAGIC_1089: i64 = 1089
28const WF_MAGIC_65536: i64 = 65536
29const WF_MAGIC_16777216: i64 = 16777216
30const WF_MAGIC_1024: i64 = 1024
31const WF_MAGIC_60000: i64 = 60000
32
33// ---- tilde-event helpers (nx_crm_flow idiom, local wf_ copies) ----
34func wf_has(hay: *u8, needle: *u8) -> i64 {
35 let n: i64 = slen(hay)
36 let m: i64 = slen(needle)
37 if m == 0 { return 0 }
38 var i: i64 = 0
39 while i + m <= n {
40 var k: i64 = 0
41 var hit: i64 = 1
42 while k < m { if hay[i+k] != needle[k] { hit = 0; k = m } else { k = k + 1 } }
43 if hit == 1 { return 1 }
44 i = i + 1
45 }
46 return 0
47}
48
49func wf_cat(dst: *u8, off: i64, s: *u8) -> i64 { var x: i64 = off; var i: i64 = 0; while s[i] != (0 as u8) { dst[x] = s[i]; x = x + 1; i = i + 1 } return x }
50func wf_catn(dst: *u8, off: i64, v: i64) -> i64 {
51 var x: i64 = off
52 var m: i64 = v
53 if m < 0 { dst[x] = 45 as u8; x = x + 1; m = 0 - m }
54 if m == 0 { dst[x] = 48 as u8; return x + 1 }
55 let t: *u8 = sys_mmap(24)
56 var k: i64 = 0
57 while m > 0 { t[k] = (48 + (m - (m/10)*10)) as u8; m = m / 10; k = k + 1 }
58 var j: i64 = 0
59 while j < k { dst[x] = t[k-1-j]; x = x + 1; j = j + 1 }
60 return x
61}
62func wf_atoi(s: *u8) -> i64 {
63 var v: i64 = 0
64 var i: i64 = 0
65 var any: i64 = 0
66 while s[i] != (0 as u8) {
67 let c: i64 = s[i] as i64
68 if c >= 48 { if c <= 57 { v = v*10 + (c - 48); any = 1 } }
69 if c < 48 { return 0 - 1 }
70 if c > 57 { return 0 - 1 }
71 i = i + 1
72 }
73 if any == 0 { return 0 - 1 }
74 return v
75}
76// raw __syscall(201) returns a broken constant on this backend (the getpid -25 class of quirk) --
77// use the proven clock_gettime wrapper so concurrent selftests never share a ledger path
78func wf_now() -> i64 { return sys_now_realtime_sec() }
79
80// ---- durable append-only ledger (flock + O_APPEND; one line per transition) ----
81// Required transition evidence: -4 must stop execution, never enter action retries.
82// fsync covers file contents; this does not claim directory-entry crash durability.
83const WF_EVIDENCE_ERROR: i64 = 0 - 4
84func wf_append(path: *u8, line: *u8) -> i64 {
85 let fd: i64 = __syscall(257, 0 - 100, path as i64, WF_MAGIC_1089, 420, 0, 0)
86 if fd < 0 { p("WFLOW evidence ERROR open failed\n"); return WF_EVIDENCE_ERROR }
87 var locked: i64 = sys_flock(fd, SYS_LOCK_EX)
88 while locked == (0 - 4) { locked = sys_flock(fd, SYS_LOCK_EX) }
89 var rc: i64 = 0
90 if locked != 0 { rc = WF_EVIDENCE_ERROR }
91 let n: i64 = slen(line)
92 var off: i64 = 0
93 while off < n {
94 if rc != 0 { break }
95 let w: i64 = sys_write(fd, (line as i64 + off) as *u8, n - off)
96 if w == (0 - 4) { continue }
97 if w <= 0 { rc = WF_EVIDENCE_ERROR } else { off = off + w }
98 }
99 if rc == 0 {
100 var sync: i64 = sys_fsync(fd)
101 while sync == (0 - 4) { sync = sys_fsync(fd) }
102 if sync != 0 { rc = WF_EVIDENCE_ERROR }
103 }
104 if locked == 0 { if sys_flock(fd, SYS_LOCK_UN) != 0 { rc = WF_EVIDENCE_ERROR } }
105 if sys_close(fd) != 0 { rc = WF_EVIDENCE_ERROR }
106 if rc != 0 { p("WFLOW evidence ERROR transition not confirmed; execution stops, reconcile ledger\n") }
107 return rc
108}
109const WF_LEDCAP: i64 = 1048576
110import "nx_wflow_text_owned.nx"
111func wf_evid(ev:*u8,out:*u8,cap:i64)->i64 {
112 if cap<=0 { return 0 }
113 out[0]=0
114 let identity:*WfOwnedText=wf_id_event(ev)
115 if identity==(0 as *WfOwnedText) { return 0 }
116 if identity.len>=cap { wf_text_free(identity);return 0 }
117 var i:i64=0
118 while i<identity.len {
119 out[i]=identity.data[i]
120 i=i+1
121 }
122 out[i]=0
123 wf_text_free(identity)
124 return 1
125}
126
127// Compatibility reader retains its historical fixed allocation for existing callers.
128func wf_readall(path: *u8, szp: *i64) -> *u8 {
129 let buf: *u8 = sys_mmap(WF_LEDCAP)
130 szp[0] = 0
131 let fd: i64 = __syscall(257, 0 - 100, path as i64, 0, 0, 0, 0)
132 if fd < 0 { if fd == (0 - 2) { return buf } return 0 as *u8 }
133 var off: i64 = 0
134 var go: i64 = 1
135 while go == 1 {
136 let r: i64 = __syscall(0, fd, (buf as i64) + off, WF_LEDCAP - off, 0, 0, 0)
137 if r == (0 - 4) { continue }
138 if r < 0 { __syscall(3, fd, 0, 0, 0, 0, 0); return 0 as *u8 }
139 if r == 0 { go = 0 }
140 if go == 1 { off = off + r; if off >= WF_LEDCAP { __syscall(3, fd, 0, 0, 0, 0, 0); p("WFLOW ledger ERROR over cap -- fail loud\n" as *u8); return 0 as *u8 } }
141 }
142 __syscall(3, fd, 0, 0, 0, 0, 0)
143 szp[0] = off
144 return buf
145}
146func wf_read_snapshot(path:*u8,szp:*i64)->*u8 {
147 szp[0]=0
148 let fd:i64=sys_openat_rd(path)
149 if fd<0 { if fd==(0-2) { let empty:*u8=sys_mmap_try(1);if (empty as i64)<=0{return 0 as *u8};empty[0]=0;return empty };return 0 as *u8 }
150 let n:i64=sys_lseek(fd,0,2)
151 if n<0||n==9223372036854775807 {sys_close(fd);return 0 as *u8}
152 if sys_lseek(fd,0,0)!=0 {sys_close(fd);return 0 as *u8}
153 let buf:*u8=sys_mmap_try(n+1)
154 if (buf as i64)<=0 {sys_close(fd);return 0 as *u8}
155 var at:i64=0
156 while at<n {
157 let got:i64=sys_read(fd,((buf as i64)+at) as *u8,n-at)
158 if got!=(0-4) {if got<=0{sys_close(fd);sys_munmap_direct(buf,n+1);p("WFLOW read ERROR short or failed snapshot; no replay\n");return 0 as *u8};at=at+got}
159 }
160 var tail:i64=sys_read(fd,((buf as i64)+n) as *u8,1)
161 while tail==(0-4){tail=sys_read(fd,((buf as i64)+n) as *u8,1)}
162 let end:i64=sys_lseek(fd,0,2);sys_close(fd)
163 if tail!=0||end!=n {sys_munmap_direct(buf,n+1);p("WFLOW read ERROR changed snapshot; no replay\n");return 0 as *u8}
164 buf[n]=0;szp[0]=n;return buf
165}
166
167func wf_writeall(path: *u8, buf: *u8, n: i64) -> i64 {
168 let fd: i64 = __syscall(257, 0 - 100, path as i64, 577, 420, 0, 0)
169 if fd < 0 { p("WFLOW write ERROR cannot open out -- fail loud\n" as *u8); return 0 - 1 }
170 let w: i64 = sys_write(fd, buf, n)
171 __syscall(3, fd, 0, 0, 0, 0, 0)
172 if w != n { p("WFLOW write ERROR short write -- fail loud\n" as *u8); return 0 - 1 }
173 return 0
174}
175
176// ---- ledger replay queries ----
177func wf_has_rng(buf: *u8, ls: i64, le: i64, needle: *u8) -> i64 {
178 let m: i64 = slen(needle)
179 if m == 0 { return 0 }
180 var i: i64 = ls
181 while i + m <= le {
182 var k: i64 = 0
183 var hit: i64 = 1
184 while k < m { if buf[i+k] != needle[k] { hit = 0; k = m } else { k = k + 1 } }
185 if hit == 1 { return 1 }
186 i = i + 1
187 }
188 return 0
189}
190// count ledger LINES containing BOTH substrings
191func wf_lines_with2(buf: *u8, n: i64, a: *u8, b: *u8) -> i64 {
192 var c: i64 = 0
193 var ls: i64 = 0
194 while ls < n {
195 var le: i64 = ls
196 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 }
197 if wf_has_rng(buf, ls, le, a) == 1 { if wf_has_rng(buf, ls, le, b) == 1 { c = c + 1 } }
198 ls = le + 1
199 }
200 return c
201}
202// parse the number after key within [ls,le); -1 if absent
203func wf_num_after(buf: *u8, ls: i64, le: i64, key: *u8) -> i64 {
204 let m: i64 = slen(key)
205 var i: i64 = ls
206 while i + m <= le {
207 var k: i64 = 0
208 var hit: i64 = 1
209 while k < m { if buf[i+k] != key[k] { hit = 0; k = m } else { k = k + 1 } }
210 if hit == 1 {
211 var q: i64 = i + m
212 var v: i64 = 0
213 var any: i64 = 0
214 while q < le { let c: i64 = buf[q] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c - 48); any = 1; q = q + 1 } else { q = le } } else { q = le } }
215 if any == 1 { return v }
216 return 0 - 1
217 }
218 i = i + 1
219 }
220 return 0 - 1
221}
222// copy the space-terminated value after key within [ls,le) into out; 1 if found
223func wf_val_after(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8, cap: i64) -> i64 {
224 let m: i64 = slen(key)
225 var i: i64 = ls
226 while i + m <= le {
227 var k: i64 = 0
228 var hit: i64 = 1
229 while k < m { if buf[i+k] != key[k] { hit = 0; k = m } else { k = k + 1 } }
230 if hit == 1 {
231 var q: i64 = i + m
232 var t: i64 = 0
233 while q < le { if buf[q] == (32 as u8) { break } if buf[q] == (10 as u8) { break } if t < cap - 1 { out[t] = buf[q]; t = t + 1 } q = q + 1 }
234 out[t] = 0 as u8
235 return 1
236 }
237 i = i + 1
238 }
239 out[0] = 0 as u8
240 return 0
241}
242// highest step idx with status=OK for this run token
243func wf_max_ok_step(buf: *u8, n: i64, ridtok: *u8) -> i64 {
244 var mx: i64 = 0
245 var ls: i64 = 0
246 while ls < n {
247 var le: i64 = ls
248 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 }
249 if wf_has_rng(buf, ls, le, ridtok) == 1 { if wf_has_rng(buf, ls, le, "status=OK" as *u8) == 1 {
250 let v: i64 = wf_num_after(buf, ls, le, " step=" as *u8)
251 if v > mx { mx = v }
252 } }
253 ls = le + 1
254 }
255 return mx
256}
257
258// ---- definition validation (whole-set, loud, nothing half-loaded) ----
259func wf_action_ok(a: *u8) -> i64 {
260 if seq(a, "create-task" as *u8) == 1 { return 1 }
261 if seq(a, "send" as *u8) == 1 { return 1 }
262 if seq(a, "notify" as *u8) == 1 { return 1 }
263 if seq(a, "update-field" as *u8) == 1 { return 1 }
264 if seq(a, "probe-fail" as *u8) == 1 { return 1 }
265 if seq(a, "exec-organ" as *u8) == 1 { return 1 }
266 if seq(a, "approve" as *u8) == 1 { return 1 }
267 if seq(a, "set-var" as *u8) == 1 { return 1 }
268 if seq(a, "for-each" as *u8) == 1 { return 1 }
269 return 0
270}
271func wf_load(steps: *i64, n: i64) -> i64 {
272 let a: *u8 = sys_mmap(64)
273 let ch: *u8 = sys_mmap(64)
274 let ix: *u8 = sys_mmap(32)
275 let ma: *u8 = sys_mmap(32)
276 var i: i64 = 0
277 while i < n {
278 let r: *u8 = steps[i] as *u8
279 pipe_field(r, 1, ix, 32)
280 if wf_atoi(ix) < 1 { p("WFLOW load ERROR bad step idx -- fail loud\n" as *u8); return 0 - 1 }
281 pipe_field(r, 2, a, 64)
282 if wf_action_ok(a) == 0 { p("WFLOW load ERROR unknown action=" as *u8); p(a); p(" -- fail loud\n" as *u8); return 0 - 1 }
283 if seq(a, "send" as *u8) == 1 {
284 pipe_field(r, 3, ch, 64)
285 if sr_valid(ch) == 0 { p("WFLOW load ERROR bad send channel=" as *u8); p(ch); p(" -- fail loud\n" as *u8); return 0 - 1 }
286 }
287 if seq(a, "exec-organ" as *u8) == 1 {
288 pipe_field(r, 3, ch, 64)
289 var chok: i64 = 1
290 if ch[0] == (0 as u8) { chok = 0 }
291 if ch[0] == (45 as u8) { if ch[1] == (0 as u8) { chok = 0 } }
292 if ch[0] == (62 as u8) { chok = 0 }
293 var gsep: i64 = 0
294 var gi2: i64 = 0
295 while ch[gi2] != (0 as u8) { if ch[gi2] == (62 as u8) { gsep = gi2 } gi2 = gi2 + 1 }
296 if gsep > 0 { if ch[gsep+1] == (0 as u8) { chok = 0 } }
297 if chok == 0 { p("WFLOW load ERROR exec-organ needs connector or connector>var -- fail loud\n" as *u8); return 0 - 1 }
298 }
299 if seq(a, "approve" as *u8) == 1 {
300 pipe_field(r, 3, ch, 64)
301 var apok: i64 = 1
302 if ch[0] == (0 as u8) { apok = 0 }
303 if ch[0] == (45 as u8) { if ch[1] == (0 as u8) { apok = 0 } }
304 if apok == 0 { p("WFLOW load ERROR approve needs an approver name -- fail loud\n" as *u8); return 0 - 1 }
305 }
306 if seq(a, "set-var" as *u8) == 1 {
307 let ag: *u8 = sys_mmap(512)
308 pipe_field(r, 4, ag, 512)
309 var haseq: i64 = 0
310 var gi: i64 = 0
311 while ag[gi] != (0 as u8) { if ag[gi] == (61 as u8) { haseq = 1 } gi = gi + 1 }
312 if haseq == 0 { p("WFLOW load ERROR set-var needs key=value -- fail loud\n" as *u8); return 0 - 1 }
313 }
314 if seq(a, "for-each" as *u8) == 1 {
315 pipe_field(r, 3, ch, 64)
316 var feok: i64 = 1
317 if ch[0] == (0 as u8) { feok = 0 }
318 if ch[0] == (45 as u8) { if ch[1] == (0 as u8) { feok = 0 } }
319 if ch[0] == (62 as u8) { feok = 0 }
320 let ag2: *u8 = sys_mmap(512)
321 pipe_field(r, 4, ag2, 512)
322 if ag2[0] == (0 as u8) { feok = 0 }
323 if feok == 0 { p("WFLOW load ERROR for-each needs connector and a list arg -- fail loud\n" as *u8); return 0 - 1 }
324 }
325 pipe_field(r, 5, ma, 32)
326 if wf_atoi(ma) < 1 { p("WFLOW load ERROR max-attempts under 1 -- fail loud\n" as *u8); return 0 - 1 }
327 // R9: optional 7th field = per-step condition, must be k=v or dash
328 let cnd: *u8 = sys_mmap(256)
329 pipe_field(r, 6, cnd, 256)
330 var cok: i64 = 1
331 if cnd[0] != (0 as u8) {
332 if cnd[0] == (45 as u8) { if cnd[1] == (0 as u8) { cok = 1 } else { cok = 0 } } else { cok = 0 }
333 if cok == 0 {
334 var hq: i64 = 0
335 var ci: i64 = 0
336 while cnd[ci] != (0 as u8) { if cnd[ci] == (61 as u8) { hq = 1 } ci = ci + 1 }
337 if hq == 1 { cok = 1 }
338 }
339 }
340 if cok == 0 { p("WFLOW load ERROR step condition must be k=v or dash -- fail loud\n" as *u8); return 0 - 1 }
341 // R10: optional 8th field = on-fail target step idx (forward-only, > own idx)
342 let onf: *u8 = sys_mmap(32)
343 pipe_field(r, 7, onf, 32)
344 var ofok: i64 = 1
345 if onf[0] != (0 as u8) {
346 var isdash: i64 = 0
347 if onf[0] == (45 as u8) { if onf[1] == (0 as u8) { isdash = 1 } }
348 if isdash == 0 {
349 let tj: i64 = wf_atoi(onf)
350 if tj < 1 { ofok = 0 }
351 pipe_field(r, 1, ix, 32)
352 if tj <= wf_atoi(ix) { ofok = 0 }
353 }
354 }
355 if ofok == 0 { p("WFLOW load ERROR on-fail target must be a LATER step idx or dash -- fail loud\n" as *u8); return 0 - 1 }
356 i = i + 1
357 }
358 return n
359}
360func wf_load_flows(flows: *i64, n: i64) -> i64 {
361 let k: *u8 = sys_mmap(128)
362 var i: i64 = 0
363 while i < n {
364 let r: *u8 = flows[i] as *u8
365 pipe_field(r, 1, k, 128)
366 if k[0] == (0 as u8) { p("WFLOW load ERROR flow without event-kind -- fail loud\n" as *u8); return 0 - 1 }
367 i = i + 1
368 }
369 return n
370}
371// does flow header match event? (kind equal + cond k=v present, or cond '-')
372func wf_match(row:*u8,ev:*u8)->i64{
373 let fk:*WfOwnedText=wf_id_pipe(row,1);let cond:*WfOwnedText=wf_id_pipe(row,2);if fk==(0 as *WfOwnedText)||cond==(0 as *WfOwnedText){wf_text_free(fk);wf_text_free(cond);return 0}
374 var n:i64=0;while ev[n]!=0&&ev[n]!=(126 as u8){n=n+1};var result:i64=0
375 if fk.len==n&&wf_text_match(ev,0,n,fk.data,n)==1{if cond.len==0||seq(cond.data,"-")==1{result=1}else{let needle:*WfOwnedText=wf_id_join("~",cond.data,"");if needle!=(0 as *WfOwnedText){let total:i64=slen(ev);var i:i64=0;while i<total{if wf_text_match(ev,i,total,needle.data,needle.len)==1{let end:i64=i+needle.len;if end==total||ev[end]==(126 as u8){result=1}};i=i+1};wf_text_free(needle)}}}
376 wf_text_free(fk);wf_text_free(cond);return result
377}
378
379func wf_emit(led: *u8, rid: *u8, fid: *u8, step: i64, st: *u8, att: i64) -> i64 {
380 let record:*WfOwnedText = wf_id_alloc(rid,fid,st,"",slen("WFRUN rid= flow= step= status= att=\n")+slen("9223372036854775807")*2+1)
381 if record==(0 as *WfOwnedText){return WF_EVIDENCE_ERROR}
382 let ln:*u8=record.data
383 var o: i64 = 0
384 o = wf_cat(ln, o, "WFRUN rid=" as *u8)
385 o = wf_cat(ln, o, rid)
386 o = wf_cat(ln, o, " flow=" as *u8)
387 o = wf_cat(ln, o, fid)
388 o = wf_cat(ln, o, " step=" as *u8)
389 o = wf_catn(ln, o, step)
390 o = wf_cat(ln, o, " status=" as *u8)
391 o = wf_cat(ln, o, st)
392 o = wf_cat(ln, o, " att=" as *u8)
393 o = wf_catn(ln, o, att)
394 ln[o] = 10 as u8
395 ln[o+1] = 0 as u8
396 let rc:i64=wf_append(led,ln)
397 wf_text_free(record)
398 return rc
399}
400
401// ---- R3 connector layer: GREEN fail-closed catalog, any blessed organ as a workflow step ----
402// catalog rows: name<TAB>elfpath<TAB>GREEN. No catalog / unknown name / flag not exactly GREEN = refuse.
403func wf_conn_resolve(cx: *i64, name: *u8, out: *u8) -> i64 {
404 let conf: *u8 = cx[3] as *u8
405 if (conf as i64) == 0 { p("WFLOW exec-organ ERROR no connector catalog loaded -- fail closed\n" as *u8); return 0 }
406 let szp: *i64 = sys_mmap(8) as *i64
407 let buf: *u8 = wf_read_snapshot(conf, szp)
408 if (buf as i64) == 0 { return 0 }
409 let n: i64 = szp[0]
410 if n == 0 { p("WFLOW exec-organ ERROR connector catalog missing or empty -- fail closed\n" as *u8); return 0 }
411 let nm: i64 = slen(name)
412 var ls: i64 = 0
413 while ls < n {
414 var le: i64 = ls
415 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 }
416 var t1: i64 = ls
417 while t1 < le { if buf[t1] == (9 as u8) { break } t1 = t1 + 1 }
418 var hit: i64 = 0
419 if t1 - ls == nm {
420 hit = 1
421 var k: i64 = 0
422 while k < nm { if buf[ls+k] != name[k] { hit = 0; k = nm } else { k = k + 1 } }
423 }
424 if hit == 1 {
425 var t2: i64 = t1 + 1
426 while t2 < le { if buf[t2] == (9 as u8) { break } t2 = t2 + 1 }
427 let fl0: i64 = t2 + 1
428 var okflag: i64 = 0
429 if le - fl0 == 5 {
430 okflag = 1
431 let g: *u8 = "GREEN" as *u8
432 var k2: i64 = 0
433 while k2 < 5 { if buf[fl0+k2] != g[k2] { okflag = 0; k2 = 5 } else { k2 = k2 + 1 } }
434 }
435 if okflag == 0 { p("WFLOW exec-organ ERROR connector=" as *u8); p(name); p(" not GREEN -- fail closed\n" as *u8); return 0 }
436 var q: i64 = t1 + 1
437 var o: i64 = 0
438 while q < t2 { if o < 500 { out[o] = buf[q]; o = o + 1 } q = q + 1 }
439 out[o] = 0 as u8
440 if o == 0 { return 0 }
441 return 1
442 }
443 ls = le + 1
444 }
445 p("WFLOW exec-organ ERROR connector unknown=" as *u8); p(name); p(" -- fail closed\n" as *u8)
446 return 0
447}
448func wf_rd32(b: *u8, off: i64) -> i64 {
449 return (b[off] as i64) + ((b[off+1] as i64) * 256) + ((b[off+2] as i64) * WF_MAGIC_65536) + ((b[off+3] as i64) * WF_MAGIC_16777216)
450}
451// run the resolved organ as the step: argv = arg split on ~ ('-' or empty = no args); exit 0 = step OK.
452// R8b: channel `connector>varname` CAPTURES child stdout (bounded 480B, newlines to spaces, loud over-cap)
453// into the run variable plane -- step OUTPUT becomes later steps' INPUT.
454func wf_exec_organ(cx: *i64, name0: *u8, arg: *u8, rid: *u8) -> i64 {
455 let name: *u8 = sys_mmap(128)
456 let capv: *u8 = sys_mmap(128)
457 var ni: i64 = 0
458 var ci: i64 = 0
459 var seen: i64 = 0
460 var i0: i64 = 0
461 while name0[i0] != (0 as u8) {
462 if name0[i0] == (62 as u8) { seen = 1 } else {
463 if seen == 0 { if ni < 126 { name[ni] = name0[i0]; ni = ni + 1 } } else { if ci < 126 { capv[ci] = name0[i0]; ci = ci + 1 } }
464 }
465 i0 = i0 + 1
466 }
467 name[ni] = 0 as u8
468 capv[ci] = 0 as u8
469 var docap: i64 = 0
470 if ci > 0 { docap = 1 }
471 let elf: *u8 = sys_mmap(512)
472 if wf_conn_resolve(cx, name, elf) == 0 { return 0 }
473 let av: *i64 = sys_mmap(8 * 16) as *i64
474 av[0] = elf as i64
475 var na: i64 = 1
476 var skip: i64 = 0
477 if arg[0] == (0 as u8) { skip = 1 }
478 if arg[0] == (45 as u8) { if arg[1] == (0 as u8) { skip = 1 } }
479 if skip == 0 {
480 let ab: *u8 = sys_mmap(WF_MAGIC_1024)
481 var i: i64 = 0
482 var over: i64 = 0
483 while arg[i] != (0 as u8) { if i < 1000 { ab[i] = arg[i] } else { over = 1 } i = i + 1 }
484 if over == 1 { p("WFLOW exec-organ ERROR argv too long -- fail closed\n" as *u8); return 0 }
485 ab[i] = 0 as u8
486 let alen: i64 = i
487 var st0: i64 = 0
488 var j: i64 = 0
489 while j <= alen {
490 var cut: i64 = 0
491 if j == alen { cut = 1 }
492 if cut == 0 { if ab[j] == (126 as u8) { cut = 1 } }
493 if cut == 1 {
494 ab[j] = 0 as u8
495 if na >= 14 { p("WFLOW exec-organ ERROR too many args -- fail closed\n" as *u8); return 0 }
496 av[na] = (ab as i64) + st0
497 na = na + 1
498 st0 = j + 1
499 }
500 j = j + 1
501 }
502 }
503 av[na] = 0
504 let fb: *u8 = sys_mmap(16)
505 if docap == 1 {
506 if __syscall(293, fb as i64, 0, 0, 0, 0, 0) != 0 { p("WFLOW capture ERROR pipe failed -- fail closed\n" as *u8); return 0 }
507 }
508 let pid: i64 = sys_fork()
509 if pid == 0 {
510 if docap == 1 {
511 let pw: i64 = wf_rd32(fb, 4)
512 sys_dup3(pw, 1, 0)
513 __syscall(3, wf_rd32(fb, 0), 0, 0, 0, 0, 0)
514 __syscall(3, pw, 0, 0, 0, 0, 0)
515 }
516 let envp: *i64 = sys_mmap(16) as *i64
517 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64
518 envp[1] = 0
519 sys_execve(elf, av, envp)
520 sys_exit(127)
521 }
522 if pid < 0 { p("WFLOW exec-organ ERROR fork failed -- fail closed\n" as *u8); return 0 }
523 let cbuf: *u8 = sys_mmap(WF_MAGIC_1024)
524 var total: i64 = 0
525 if docap == 1 {
526 let pr: i64 = wf_rd32(fb, 0)
527 __syscall(3, wf_rd32(fb, 4), 0, 0, 0, 0, 0)
528 var go: i64 = 1
529 while go == 1 {
530 let rr: i64 = __syscall(0, pr, (cbuf as i64) + total, 1023 - total, 0, 0, 0)
531 if rr <= 0 { go = 0 }
532 if go == 1 { total = total + rr; if total >= 1023 { go = 0 } }
533 }
534 __syscall(3, pr, 0, 0, 0, 0, 0)
535 }
536 let st: *i64 = sys_mmap(16) as *i64
537 var waited: i64 = sys_wait4(pid, st, 0)
538 while waited == (0 - 4) { waited = sys_wait4(pid, st, 0) }
539 if waited != pid { p("WFLOW exec-organ ERROR child wait failed -- fail closed\n" as *u8); return 0 }
540 let ec: i64 = wait_status_rc(st[0])
541 p("WFLOW-EXEC-ORGAN connector=" as *u8); p(name); p(" elf=" as *u8); p(elf); p(" exit=" as *u8); pn(ec); p("\n" as *u8)
542 if ec != 0 { return 0 }
543 if docap == 1 {
544 if total > 480 { p("WFLOW capture ERROR output over 480 bytes -- fail loud\n" as *u8); return 0 }
545 var t: i64 = 0
546 while t < total { if cbuf[t] == (10 as u8) { cbuf[t] = 32 as u8 } if cbuf[t] == (13 as u8) { cbuf[t] = 32 as u8 } t = t + 1 }
547 while total > 0 { if cbuf[total-1] == (32 as u8) { total = total - 1 } else { break } }
548 cbuf[total] = 0 as u8
549 let ledc: *u8 = cx[2] as *u8
550 if wf_emit_var(ledc, rid, capv, cbuf) != 0 { return WF_EVIDENCE_ERROR }
551 p("WFLOW-CAPTURE run=" as *u8); p(rid); p(" var=" as *u8); p(capv); p(" v=" as *u8); p(cbuf); p("\n" as *u8)
552 }
553 return 1
554}
555
556// ---- R11 loops: apply-to-each over the connector plane ----
557func wf_emit_iter(led: *u8, rid: *u8, n: i64, val: *u8) -> i64 {
558 let number:*WfOwnedText=wf_text_decimal(n)
559 if number==(0 as *WfOwnedText){return WF_EVIDENCE_ERROR}
560 let rn:i64=slen(rid);let vn:i64=slen(val)
561 var bytes:i64=slen("WFITER rid=")+slen(" n=")+slen(" item=")+2
562 if number.len>WF_TEXT_I64_MAX-bytes{wf_text_free(number);return WF_EVIDENCE_ERROR};bytes=bytes+number.len
563 if rn>WF_TEXT_I64_MAX-bytes{wf_text_free(number);return WF_EVIDENCE_ERROR};bytes=bytes+rn
564 if vn>WF_TEXT_I64_MAX-bytes{wf_text_free(number);return WF_EVIDENCE_ERROR};bytes=bytes+vn
565 let line:*u8=sys_mmap_try(bytes)
566 if (line as i64)<=0{wf_text_free(number);return WF_EVIDENCE_ERROR}
567 var at:i64=wf_cat(line,0,"WFITER rid=")
568 at=wf_cat(line,at,rid);at=wf_cat(line,at," n=");at=wf_cat(line,at,number.data)
569 at=wf_cat(line,at," item=");at=wf_cat(line,at,val);line[at]=10 as u8;line[at+1]=0
570 var rc:i64=wf_append(led,line)
571 if sys_munmap_direct(line,bytes)!=0{rc=WF_EVIDENCE_ERROR}
572 if wf_text_free(number)!=0{rc=WF_EVIDENCE_ERROR}
573 return rc
574}
575// for-each: arg = <listexpr>~<per-item template> (template default {item}). The list substitutes ONCE
576// (comma-split, empties skipped; input-sized storage); per item: bind {item} as a run var, emit WFITER,
577// substitute the template, fork the connector. Any failed item fails the STEP (retry/on-fail apply).
578// Empty list = zero iterations, step OK (apply-to-each on an empty collection is a no-op).
579func wf_for_each(cx: *i64, chspec: *u8, argraw: *u8, rid: *u8) -> i64 {
580 let led:*u8=cx[2] as *u8;let total:i64=slen(argraw)
581 var sep:i64=0;while sep<total{if argraw[sep]==(126 as u8){break};sep=sep+1}
582 let list:*WfOwnedText=wf_text_slice(argraw,0,sep)
583 if list==(0 as *WfOwnedText){return WF_EVIDENCE_ERROR}
584 var tpl:*WfOwnedText=0 as *WfOwnedText
585 if sep<total{tpl=wf_text_slice(argraw,sep+1,total-sep-1)}else{tpl=wf_text_slice("{item}",0,slen("{item}"))}
586 if tpl==(0 as *WfOwnedText){wf_text_free(list);return WF_EVIDENCE_ERROR}
587 let expanded:*WfOwnedText=wf_subst_owned(cx,rid,list.data)
588 wf_text_free(list)
589 if expanded==(0 as *WfOwnedText){wf_text_free(tpl);return 0}
590 var rc:i64=1;var start:i64=0;var cursor:i64=0;var item:i64=0
591 while cursor<=expanded.len{
592 var cut:i64=0
593 if cursor==expanded.len{cut=1}else{if expanded.data[cursor]==(44 as u8){cut=1}}
594 if cut==1{
595 expanded.data[cursor]=0
596 if cursor>start{
597 let value:*u8=((expanded.data as i64)+start) as *u8;item=item+1
598 if wf_emit_var(led,rid,"item",value)!=0{rc=WF_EVIDENCE_ERROR;break}
599 if wf_emit_iter(led,rid,item,value)!=0{rc=WF_EVIDENCE_ERROR;break}
600 let prepared:*WfOwnedText=wf_subst_owned(cx,rid,tpl.data)
601 if prepared==(0 as *WfOwnedText){rc=0;break}
602 let result:i64=wf_exec_organ(cx,chspec,prepared.data,rid)
603 if wf_text_free(prepared)!=0{rc=WF_EVIDENCE_ERROR;break}
604 if result!=1{rc=result;break}
605 }
606 start=cursor+1
607 }
608 cursor=cursor+1
609 }
610 if wf_text_free(expanded)!=0{rc=WF_EVIDENCE_ERROR}
611 if wf_text_free(tpl)!=0{rc=WF_EVIDENCE_ERROR}
612 return rc
613}
614
615// ---- action execution (in-process v1 + exec-organ; send-channel semantics from nx_send) ----
616func wf_exec(cx: *i64, action: *u8, ch: *u8, arg: *u8, att: i64, rid: *u8) -> i64 {
617 p("WFLOW-ACTION action=" as *u8); p(action)
618 if seq(action, "send" as *u8) == 1 { p(" channel=" as *u8); p(ch); p(" route=" as *u8); p(sr_route(ch)) }
619 p(" arg=" as *u8); p(arg)
620 p(" att=" as *u8); pn(att)
621 p("\n" as *u8)
622 if seq(action, "probe-fail" as *u8) == 1 {
623 let k: i64 = wf_atoi(arg)
624 if att < k { return 0 }
625 return 1
626 }
627 if seq(action, "exec-organ" as *u8) == 1 { return wf_exec_organ(cx, ch, arg, rid) }
628 if seq(action, "for-each" as *u8) == 1 { return wf_for_each(cx, ch, arg, rid) }
629 return 1
630}
631
632// ---- R4 human-in-the-loop approvals: WFDEC decision records on the same ledger ----
633// WFDEC rid=<rid> step=<n> decision=APPROVED|DENIED by=<who>. DENY WINS if both exist (fail-safe,
634// deny-by-default). No decision = the approve step PARKS the run (status=WAIT, in-flight); wf_resume
635// re-examines it and completes once a decision lands.
636func wf_decision(cx: *i64, rid: *u8, idx: i64) -> i64 {
637 let led: *u8 = cx[2] as *u8
638 let szp: *i64 = sys_mmap_try(__size_of(i64)) as *i64
639 if (szp as i64)<=0{return WF_EVIDENCE_ERROR}
640 let buf: *u8 = wf_read_snapshot(led, szp)
641 if (buf as i64) == 0 { sys_munmap_direct(szp as *u8,8); return 0 }
642 let token:*WfOwnedText=wf_id_tok(rid)
643 if token==(0 as *WfOwnedText){sys_munmap_direct(buf,szp[0]+1);sys_munmap_direct(szp as *u8,8);return WF_EVIDENCE_ERROR}
644 let tok:*u8=token.data
645 var q: i64 = 0
646 q = wf_cat(tok, q, " rid=" as *u8)
647 q = wf_cat(tok, q, rid)
648 tok[q] = 32 as u8
649 tok[q+1] = 0 as u8
650 let nb: *u8 = sys_mmap_try(128)
651 if (nb as i64)<=0{wf_text_free(token);sys_munmap_direct(buf,szp[0]+1);sys_munmap_direct(szp as *u8,__size_of(i64));return WF_EVIDENCE_ERROR}
652 var o: i64 = 0
653 o = wf_cat(nb, o, " step=" as *u8)
654 o = wf_catn(nb, o, idx)
655 o = wf_cat(nb, o, " decision=DENIED" as *u8)
656 nb[o] = 0 as u8
657 var result:i64=0
658 if wf_lines_with2(buf, szp[0], tok, nb) > 0 { result=0-1 }
659 var o2: i64 = 0
660 o2 = wf_cat(nb, o2, " step=" as *u8)
661 o2 = wf_catn(nb, o2, idx)
662 o2 = wf_cat(nb, o2, " decision=APPROVED" as *u8)
663 nb[o2] = 0 as u8
664 if result==0&&wf_lines_with2(buf, szp[0], tok, nb) > 0 { result=1 }
665 sys_munmap_direct(nb,128);wf_text_free(token);sys_munmap_direct(buf,szp[0]+1);sys_munmap_direct(szp as *u8,8)
666 return result
667}
668// the operator surface: append a decision record (only APPROVED or DENIED accepted, loud else)
669func wf_decide(cx: *i64, rid: *u8, idx: i64, decision: *u8, who: *u8) -> i64 {
670 var okd: i64 = 0
671 if seq(decision, "APPROVED" as *u8) == 1 { okd = 1 }
672 if seq(decision, "DENIED" as *u8) == 1 { okd = 1 }
673 if okd == 0 { p("WFLOW decide ERROR decision must be APPROVED or DENIED -- fail loud\n" as *u8); return 0 - 1 }
674 let led: *u8 = cx[2] as *u8
675 let record:*WfOwnedText = wf_id_alloc(rid,decision,who,"",slen("WFDEC rid= step= decision= by=\n")+slen("9223372036854775807")+1)
676 if record==(0 as *WfOwnedText){return WF_EVIDENCE_ERROR}
677 let ln:*u8=record.data
678 var o: i64 = 0
679 o = wf_cat(ln, o, "WFDEC rid=" as *u8)
680 o = wf_cat(ln, o, rid)
681 o = wf_cat(ln, o, " step=" as *u8)
682 o = wf_catn(ln, o, idx)
683 o = wf_cat(ln, o, " decision=" as *u8)
684 o = wf_cat(ln, o, decision)
685 o = wf_cat(ln, o, " by=" as *u8)
686 o = wf_cat(ln, o, who)
687 ln[o] = 10 as u8
688 ln[o+1] = 0 as u8
689 let rc:i64=wf_append(led,ln)
690 wf_text_free(record)
691 return rc
692}
693
694// ---- R8 data passing: run-scoped variables on the SAME event-sourced ledger ----
695// WFVAR rid=<rid> k=<key> v=<value-to-end-of-line>. Trigger-event fields auto-bind at fire; a set-var
696// step writes derived vars; {key} placeholders in any step arg substitute at execution; LAST write wins;
697// unknown placeholder = LOUD step failure (the send_merge law). Values must not contain ~ or newline.
698func wf_emit_var(led: *u8, rid: *u8, k: *u8, v: *u8) -> i64 {
699 // Allocate the complete serialized record, including newline and terminator.
700 var bytes: i64 = slen("WFVAR rid=" as *u8)+slen(" k=" as *u8)+slen(" v=" as *u8)+2
701 let rn: i64 = slen(rid);let kn: i64 = slen(k);let vn: i64 = slen(v)
702 if rn>9223372036854775807-bytes{return 0-1};bytes=bytes+rn
703 if kn>9223372036854775807-bytes{return 0-1};bytes=bytes+kn
704 if vn>9223372036854775807-bytes{return 0-1};bytes=bytes+vn
705 let ln: *u8 = sys_mmap_try(bytes)
706 if (ln as i64)<=0{p("WFLOW variable ERROR allocation refused; record not appended\n" as *u8);return 0-1}
707 var o: i64 = 0
708 o = wf_cat(ln, o, "WFVAR rid=" as *u8)
709 o = wf_cat(ln, o, rid)
710 o = wf_cat(ln, o, " k=" as *u8)
711 o = wf_cat(ln, o, k)
712 o = wf_cat(ln, o, " v=" as *u8)
713 o = wf_cat(ln, o, v)
714 ln[o] = 10 as u8
715 ln[o+1] = 0 as u8
716 let rc:i64=wf_append(led,ln)
717 let released:i64=sys_munmap_direct(ln,bytes)
718 if released!=0{return 0-1}
719 return rc
720}
721// copy value after key to END OF LINE (values may contain spaces)
722func wf_val_line_end(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8, cap: i64) -> i64 {
723 let m: i64 = slen(key)
724 var i: i64 = ls
725 while i + m <= le {
726 var k: i64 = 0
727 var hit: i64 = 1
728 while k < m { if buf[i+k] != key[k] { hit = 0; k = m } else { k = k + 1 } }
729 if hit == 1 {
730 var q: i64 = i + m
731 var t: i64 = 0
732 while q < le { if t < cap - 1 { out[t] = buf[q]; t = t + 1 } q = q + 1 }
733 out[t] = 0 as u8
734 return 1
735 }
736 i = i + 1
737 }
738 out[0] = 0 as u8
739 return 0
740}
741func wf_obs_tok_fwd(tok: *u8, rid: *u8) -> i64 {
742 var q: i64 = 0
743 q = wf_cat(tok, q, " rid=" as *u8)
744 q = wf_cat(tok, q, rid)
745 tok[q] = 32 as u8
746 tok[q+1] = 0 as u8
747 return q
748}
749// Latest WFVAR value: 1 found, 0 missing, -1 unresolved or insufficient capacity; no partial output.
750func wf_var_get(cx: *i64, rid: *u8, key: *u8, out: *u8, cap: i64) -> i64 {
751 if cap<=0{return 0-1};out[0]=0
752 let owned:*WfOwnedText=wf_var_owned(cx,rid,key)
753 if owned==(0 as *WfOwnedText){return 0-1}
754 var rc:i64=owned.status
755 if owned.status==1{
756 if owned.len>=cap{rc=0-1}else{var i:i64=0;while i<owned.len{out[i]=owned.data[i];i=i+1};out[owned.len]=0}
757 }
758 if wf_text_free(owned)!=0{out[0]=0;return 0-1}
759 return rc
760}
761// Substitute {key} from one owned ledger snapshot; -1 on unresolved input, allocation or insufficient output.
762func wf_subst(cx: *i64, rid: *u8, src: *u8, out: *u8, cap: i64) -> i64 {
763 if cap<=0{return 0-1};out[0]=0
764 let owned:*WfOwnedText=wf_subst_owned(cx,rid,src)
765 if owned==(0 as *WfOwnedText){return 0-1}
766 var rc:i64=owned.len
767 if owned.len>=cap{rc=0-1}else{var i:i64=0;while i<owned.len{out[i]=owned.data[i];i=i+1};out[owned.len]=0}
768 if wf_text_free(owned)!=0{out[0]=0;return 0-1}
769 return rc
770}
771// bind every k=v field of the trigger event as an initial run variable (segment 0 = kind, skipped)
772func wf_bind_event(cx: *i64, rid: *u8, ev: *u8) -> i64 {
773 let n:i64=slen(ev)
774 if n==9223372036854775807{return WF_EVIDENCE_ERROR}
775 let bytes:i64=n+1
776 let kb:*u8=sys_mmap_try(bytes)
777 if (kb as i64)<=0{return WF_EVIDENCE_ERROR}
778 let vb:*u8=sys_mmap_try(bytes)
779 if (vb as i64)<=0{sys_munmap_direct(kb,bytes);return WF_EVIDENCE_ERROR}
780 var i:i64=0
781 var bound:i64=0
782 var result:i64=0
783 while i<n {
784 if ev[i]!=(126 as u8){i=i+1}
785 else {
786 var j:i64=i+1
787 var kn:i64=0
788 var haseq:i64=0
789 while j<n {
790 if ev[j]==(126 as u8){break}
791 if ev[j]==(61 as u8){haseq=1;j=j+1;break}
792 kb[kn]=ev[j];kn=kn+1;j=j+1
793 }
794 kb[kn]=0 as u8
795 if haseq==1 {
796 var vn:i64=0
797 while j<n {
798 if ev[j]==(126 as u8){break}
799 vb[vn]=ev[j];vn=vn+1;j=j+1
800 }
801 vb[vn]=0 as u8
802 if kn>0 {
803 if wf_emit_var(cx[2] as *u8,rid,kb,vb)!=0{result=WF_EVIDENCE_ERROR;break}
804 bound=bound+1
805 }
806 }
807 i=j
808 }
809 }
810 let kr:i64=sys_munmap_direct(kb,bytes)
811 let vr:i64=sys_munmap_direct(vb,bytes)
812 if result!=0||kr!=0||vr!=0{return WF_EVIDENCE_ERROR}
813 return bound
814}
815
816// ---- R12 versioning: @version N directive in the flows file; runs stamp WFVER; resume flags drift ----
817func wf_defs_version(path: *u8) -> i64 {
818 let szp: *i64 = sys_mmap(8) as *i64
819 let buf: *u8 = wf_read_snapshot(path, szp)
820 if (buf as i64) == 0 { return 0 }
821 let n: i64 = szp[0]
822 var ls: i64 = 0
823 while ls < n {
824 var le: i64 = ls
825 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 }
826 if wf_has_rng(buf, ls, le, "@version " as *u8) == 1 {
827 let v: i64 = wf_num_after(buf, ls, le, "@version " as *u8)
828 if v > 0 { return v }
829 }
830 ls = le + 1
831 }
832 return 0
833}
834func wf_emit_ver(led: *u8, rid: *u8, ver: i64) -> i64 {
835 let record:*WfOwnedText = wf_id_alloc(rid,"","","",slen("WFVER rid= ver=\n")+slen("9223372036854775807")+1)
836 if record==(0 as *WfOwnedText){return WF_EVIDENCE_ERROR}
837 let ln:*u8=record.data
838 var o: i64 = 0
839 o = wf_cat(ln, o, "WFVER rid=" as *u8)
840 o = wf_cat(ln, o, rid)
841 o = wf_cat(ln, o, " ver=" as *u8)
842 o = wf_catn(ln, o, ver)
843 ln[o] = 10 as u8
844 ln[o+1] = 0 as u8
845 let rc:i64=wf_append(led,ln)
846 wf_text_free(record)
847 return rc
848}
849func wf_emit_drift(led: *u8, rid: *u8, ranv: i64, nowv: i64) -> i64 {
850 let record:*WfOwnedText = wf_id_alloc(rid,"","","",slen("WFVERDRIFT rid= ran= now=\n")+slen("9223372036854775807")*2+1)
851 if record==(0 as *WfOwnedText){return WF_EVIDENCE_ERROR}
852 let ln:*u8=record.data
853 var o: i64 = 0
854 o = wf_cat(ln, o, "WFVERDRIFT rid=" as *u8)
855 o = wf_cat(ln, o, rid)
856 o = wf_cat(ln, o, " ran=" as *u8)
857 o = wf_catn(ln, o, ranv)
858 o = wf_cat(ln, o, " now=" as *u8)
859 o = wf_catn(ln, o, nowv)
860 ln[o] = 10 as u8
861 ln[o+1] = 0 as u8
862 let rc:i64=wf_append(led,ln)
863 wf_text_free(record)
864 return rc
865}
866// the version a run was STARTED under (last WFVER line for the rid; 0 = unversioned)
867func wf_run_ver(buf: *u8, n: i64, ridtok: *u8) -> i64 {
868 var v: i64 = 0
869 var ls: i64 = 0
870 while ls < n {
871 var le: i64 = ls
872 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 }
873 if wf_has_rng(buf, ls, le, "WFVER " as *u8) == 1 { if wf_has_rng(buf, ls, le, ridtok) == 1 {
874 let x: i64 = wf_num_after(buf, ls, le, " ver=" as *u8)
875 if x > 0 { v = x }
876 } }
877 ls = le + 1
878 }
879 return v
880}
881
882// ---- R13 templates: gallery = <dir>/index (name|description rows) + <name>.flows/.steps pairs ----
883func wf_tpl_exists(path: *u8) -> i64 {
884 let fd: i64 = __syscall(257, 0 - 100, path as i64, 0, 0, 0, 0)
885 if fd < 0 { return 0 }
886 __syscall(3, fd, 0, 0, 0, 0, 0)
887 return 1
888}
889func wf_tpl_path(dir: *u8, name: *u8, ext: *u8, out: *u8) -> i64 {
890 var o: i64 = 0
891 o = wf_cat(out, o, dir)
892 out[o] = 47 as u8
893 o = o + 1
894 o = wf_cat(out, o, name)
895 o = wf_cat(out, o, ext)
896 out[o] = 0 as u8
897 return o
898}
899func wf_tpl_index_path(dir: *u8, out: *u8) -> i64 {
900 var o: i64 = 0
901 o = wf_cat(out, o, dir)
902 out[o] = 47 as u8
903 o = o + 1
904 o = wf_cat(out, o, "index" as *u8)
905 out[o] = 0 as u8
906 return o
907}
908// list the gallery: every index row printed with file-existence + version; returns USABLE count, -1 loud
909func wf_tpl_list(dir: *u8) -> i64 {
910 let ip: *u8 = sys_mmap(512)
911 wf_tpl_index_path(dir, ip)
912 let szp: *i64 = sys_mmap(8) as *i64
913 let buf: *u8 = wf_read_snapshot(ip, szp)
914 if (buf as i64) == 0 { return 0 - 1 }
915 let n: i64 = szp[0]
916 if n == 0 { p("WFLOW templates ERROR missing gallery index -- fail loud\n" as *u8); return 0 - 1 }
917 let nm: *u8 = sys_mmap(128)
918 let ds: *u8 = sys_mmap(512)
919 let fp2: *u8 = sys_mmap(512)
920 let sp2: *u8 = sys_mmap(512)
921 var cnt: i64 = 0
922 var ls: i64 = 0
923 while ls < n {
924 var le: i64 = ls
925 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 }
926 var keep: i64 = 0
927 if le > ls { keep = 1 }
928 if keep == 1 { if buf[ls] == (35 as u8) { keep = 0 } }
929 if keep == 1 {
930 let row: *u8 = sys_mmap(le - ls + 2)
931 var k: i64 = 0
932 while ls + k < le { row[k] = buf[ls+k]; k = k + 1 }
933 row[k] = 0 as u8
934 pipe_field(row, 0, nm, 128)
935 pipe_field(row, 1, ds, 512)
936 wf_tpl_path(dir, nm, ".flows" as *u8, fp2)
937 wf_tpl_path(dir, nm, ".steps" as *u8, sp2)
938 let fe: i64 = wf_tpl_exists(fp2)
939 let se: i64 = wf_tpl_exists(sp2)
940 let v: i64 = wf_defs_version(fp2)
941 p("WFTPL name=" as *u8); p(nm)
942 p(" ver=" as *u8); pn(v)
943 p(" flows=" as *u8); pn(fe)
944 p(" steps=" as *u8); pn(se)
945 p(" desc=" as *u8); p(ds)
946 p("\n" as *u8)
947 if fe == 1 { if se == 1 { cnt = cnt + 1 } }
948 }
949 ls = le + 1
950 }
951 p("WFTPL-TOTAL usable=" as *u8); pn(cnt); p("\n" as *u8)
952 return cnt
953}
954func wf_tpl_indexed(dir: *u8, name: *u8) -> i64 {
955 let ip: *u8 = sys_mmap(512)
956 wf_tpl_index_path(dir, ip)
957 let szp: *i64 = sys_mmap(8) as *i64
958 let buf: *u8 = wf_read_snapshot(ip, szp)
959 if (buf as i64) == 0 { return 0 - 1 }
960 let n: i64 = szp[0]
961 if n == 0 { return 0 - 1 }
962 let nm: *u8 = sys_mmap(128)
963 var ls: i64 = 0
964 while ls < n {
965 var le: i64 = ls
966 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 }
967 if le > ls { if buf[ls] != (35 as u8) {
968 let row: *u8 = sys_mmap(le - ls + 2)
969 var k: i64 = 0
970 while ls + k < le { row[k] = buf[ls+k]; k = k + 1 }
971 row[k] = 0 as u8
972 pipe_field(row, 0, nm, 128)
973 if seq(nm, name) == 1 { return 1 }
974 } }
975 ls = le + 1
976 }
977 return 0
978}
979// instantiate: fail-closed to INDEXED templates; byte-copy both files; whole-set validate the COPY
980// (a broken template never lands silently). Returns the template version (0 = unversioned), -1 loud.
981func wf_tpl_instantiate(dir: *u8, name: *u8, df: *u8, ds: *u8) -> i64 {
982 let ix: i64 = wf_tpl_indexed(dir, name)
983 if ix != 1 { p("WFLOW instantiate ERROR template not in gallery index -- fail closed\n" as *u8); return 0 - 1 }
984 let fp2: *u8 = sys_mmap(512)
985 let sp2: *u8 = sys_mmap(512)
986 wf_tpl_path(dir, name, ".flows" as *u8, fp2)
987 wf_tpl_path(dir, name, ".steps" as *u8, sp2)
988 let szp: *i64 = sys_mmap(8) as *i64
989 let fb: *u8 = wf_read_snapshot(fp2, szp)
990 if (fb as i64) == 0 { return 0 - 1 }
991 if szp[0] == 0 { p("WFLOW instantiate ERROR template flows file missing -- fail loud\n" as *u8); return 0 - 1 }
992 if wf_writeall(df, fb, szp[0]) != 0 { return 0 - 1 }
993 let sb: *u8 = wf_read_snapshot(sp2, szp)
994 if (sb as i64) == 0 { return 0 - 1 }
995 if szp[0] == 0 { p("WFLOW instantiate ERROR template steps file missing -- fail loud\n" as *u8); return 0 - 1 }
996 if wf_writeall(ds, sb, szp[0]) != 0 { return 0 - 1 }
997 let fl: *i64 = sys_mmap(8 * 64) as *i64
998 let st2: *i64 = sys_mmap(8 * 128) as *i64
999 let nf: i64 = wf_lines_load(df, fl, 64)
1000 if nf < 1 { return 0 - 1 }
1001 let nst: i64 = wf_lines_load(ds, st2, 128)
1002 if nst < 1 { return 0 - 1 }
1003 if wf_load_flows(fl, nf) < 0 { return 0 - 1 }
1004 if wf_load(st2, nst) < 0 { return 0 - 1 }
1005 let v: i64 = wf_defs_version(df)
1006 p("WFLOW-INSTANTIATE template=" as *u8); p(name); p(" version=" as *u8); pn(v); p(" flows=" as *u8); pn(nf); p(" steps=" as *u8); pn(nst); p("\n" as *u8)
1007 return v
1008}
1009
1010// R9 branching: per-step condition k=v against the run variable plane. Missing variable or
1011// mismatch = 0 (the step SKIPs, recorded in the ledger); match = 1. Equality only, v1.
1012func wf_cond_ok(cx: *i64, rid: *u8, cond: *u8) -> i64 {
1013 let n:i64=slen(cond);var split:i64=0
1014 while split<n{if cond[split]==(61 as u8){break};split=split+1}
1015 if split==0{return 0};if split==n{return 0}
1016 let key:*WfOwnedText=wf_text_slice(cond,0,split)
1017 if key==(0 as *WfOwnedText){return 0-1}
1018 let value:*WfOwnedText=wf_var_owned(cx,rid,key.data)
1019 wf_text_free(key)
1020 if value==(0 as *WfOwnedText){return 0-1}
1021 var result:i64=0
1022 if value.status==1{result=seq(value.data,((cond as i64)+split+1) as *u8)}
1023 if value.status<0{result=0-1}
1024 if wf_text_free(value)!=0{return 0-1};return result
1025}
1026
1027// ---- the durable executor: run flow fid for run rid, starting AFTER step s0 ----
1028// cx bundle: cx[0]=steps ptr, cx[1]=nsteps, cx[2]=ledger path
1029func wf_run_from(cx: *i64, rid: *u8, fid: *u8, s0: i64) -> i64 {
1030 let steps:*i64=cx[0] as *i64;var i:i64=0;var longest:i64=0
1031 while i<cx[1]{let n:i64=slen(steps[i] as *u8);if n>longest{longest=n};i=i+1}
1032 if longest==WF_TEXT_I64_MAX{return WF_EVIDENCE_ERROR}
1033 let scratch:*WfRunScratch=wf_run_scratch_new(longest+1)
1034 if scratch==(0 as *WfRunScratch){return WF_EVIDENCE_ERROR}
1035 let rc:i64=wf_run_from_owned(cx,rid,fid,s0,scratch)
1036 if wf_run_scratch_free(scratch)!=0{return WF_EVIDENCE_ERROR}
1037 return rc
1038}
1039
1040func wf_run_from_owned(cx: *i64, rid: *u8, fid: *u8, s0: i64, scratch:*WfRunScratch) -> i64 {
1041 let steps: *i64 = cx[0] as *i64
1042 let ns: i64 = cx[1]
1043 let led: *u8 = cx[2] as *u8
1044 let f2: *u8 = scratch.fields.fid
1045 let ix: *u8 = scratch.fields.index
1046 let a: *u8 = scratch.fields.action
1047 let ch: *u8 = scratch.fields.channel
1048 let arg: *u8 = scratch.fields.arg
1049 let ma: *u8 = scratch.fields.retry
1050 var i: i64 = 0
1051 var done: i64 = 0
1052 var failed: i64 = 0
1053 var parked: i64 = 0
1054 var jumpto: i64 = 0
1055 while i < ns {
1056 if wf_text_free(scratch.prepared)!=0{return WF_EVIDENCE_ERROR};scratch.prepared=0 as *WfOwnedText
1057 let r: *u8 = steps[i] as *u8
1058 pipe_field(r, 0, f2, scratch.cap)
1059 var use: i64 = 0
1060 if seq(f2, fid) == 1 { use = 1 }
1061 var idx: i64 = 0
1062 if use == 1 { pipe_field(r, 1, ix, scratch.cap); idx = wf_atoi(ix); if idx <= s0 { use = 0 } }
1063 // R10: an active on-fail jump skips the remaining normal-path steps (recorded) until the target
1064 if use == 1 { if jumpto > 0 { if idx < jumpto { if wf_emit(led, rid, fid, idx, "SKIP" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }; use = 0 } else { jumpto = 0 } } }
1065 if use == 1 {
1066 pipe_field(r, 2, a, scratch.cap)
1067 pipe_field(r, 3, ch, scratch.cap)
1068 pipe_field(r, 4, arg, scratch.cap)
1069 pipe_field(r, 5, ma, scratch.cap)
1070 // R9: optional per-step condition (7th field, k=v) vs the variable plane -> SKIP on mismatch
1071 let cond: *u8 = scratch.fields.condition
1072 pipe_field(r, 6, cond, scratch.cap)
1073 var argx: *u8 = arg
1074 var mode: i64 = 0
1075 var docond: i64 = 0
1076 if cond[0] != (0 as u8) { docond = 1 }
1077 if cond[0] == (45 as u8) { if cond[1] == (0 as u8) { docond = 0 } }
1078 if docond == 1 { let condition:i64=wf_cond_ok(cx,rid,cond);if condition==0{mode=4};if condition<0{mode=3} }
1079 // R8: resolve {key} placeholders from the run variable plane; unknown = LOUD step failure.
1080 // for-each bypasses step-level subst ({item} binds inside the loop) -- the loop substitutes
1081 // the list once and the per-item template each iteration.
1082 if mode == 0 {
1083 var rawarg: i64 = 0
1084 if seq(a, "for-each" as *u8) == 1 { rawarg = 1 }
1085 if rawarg==0 {
1086 scratch.prepared=wf_subst_owned(cx,rid,arg)
1087 if scratch.prepared==(0 as *WfOwnedText){mode=3}else{argx=scratch.prepared.data}
1088 }
1089 }
1090 if mode == 4 {
1091 p("WFLOW-SKIP run=" as *u8); p(rid); p(" step=" as *u8); pn(idx); p(" cond=" as *u8); p(cond); p("\n" as *u8)
1092 if wf_emit(led, rid, fid, idx, "SKIP" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1093 }
1094 if mode == 0 { if seq(a, "set-var" as *u8) == 1 { mode = 2 } }
1095 if mode == 0 { if seq(a, "approve" as *u8) == 1 { mode = 1 } }
1096 if mode == 3 {
1097 if wf_emit(led, rid, fid, idx, "FAILSTEP" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1098 if wf_emit(led, rid, fid, idx, "FAILED" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1099 failed = 1
1100 i = ns
1101 }
1102 if mode == 2 {
1103 var e:i64=0
1104 while argx[e]!=(0 as u8){if argx[e]==(61 as u8){break};e=e+1}
1105 if argx[e]==(61 as u8){if e>0{
1106 argx[e]=0
1107 let kb:*u8=argx
1108 let vb:*u8=((argx as i64)+e+1) as *u8
1109 if wf_emit_var(led, rid, kb, vb) != 0 { return WF_EVIDENCE_ERROR }
1110 p("WFLOW-SETVAR run=" as *u8); p(rid); p(" k=" as *u8); p(kb); p(" v=" as *u8); p(vb); p("\n" as *u8)
1111 if wf_emit(led, rid, fid, idx, "OK" as *u8, 1) != 0 { return WF_EVIDENCE_ERROR }
1112 done = done + 1
1113 } else { mode = 3 } } else { mode = 3 }
1114 if mode == 3 {
1115 p("WFLOW set-var ERROR needs key=value -- fail loud\n" as *u8)
1116 if wf_emit(led, rid, fid, idx, "FAILSTEP" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1117 if wf_emit(led, rid, fid, idx, "FAILED" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1118 failed = 1
1119 i = ns
1120 }
1121 }
1122 if mode == 1 {
1123 let dec: i64 = wf_decision(cx, rid, idx)
1124 if dec == 1 {
1125 p("WFLOW-APPROVAL granted run=" as *u8); p(rid); p(" step=" as *u8); pn(idx); p("\n" as *u8)
1126 if wf_emit(led, rid, fid, idx, "OK" as *u8, 1) != 0 { return WF_EVIDENCE_ERROR }
1127 done = done + 1
1128 }
1129 if dec == (0 - 1) {
1130 p("WFLOW-APPROVAL DENIED run=" as *u8); p(rid); p(" step=" as *u8); pn(idx); p(" -- run fails (deny wins)\n" as *u8)
1131 if wf_emit(led, rid, fid, idx, "FAILSTEP" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1132 if wf_emit(led, rid, fid, idx, "FAILED" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1133 failed = 1
1134 i = ns
1135 }
1136 if dec == 0 {
1137 p("WFLOW-APPROVAL pending run=" as *u8); p(rid); p(" step=" as *u8); pn(idx)
1138 p(" approver=" as *u8); p(ch); p(" what=" as *u8); p(argx); p(" -- run PARKED\n" as *u8)
1139 if wf_emit(led, rid, fid, idx, "WAIT" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1140 parked = 1
1141 i = ns
1142 }
1143 }
1144 if mode == 0 {
1145 let maxa: i64 = wf_atoi(ma)
1146 var att: i64 = 1
1147 var okd: i64 = 0
1148 while att <= maxa {
1149 if wf_emit(led, rid, fid, idx, "ATT" as *u8, att) != 0 { return WF_EVIDENCE_ERROR }
1150 let rc: i64 = wf_exec(cx, a, ch, argx, att, rid)
1151 if rc == WF_EVIDENCE_ERROR { return rc }
1152 if rc == 1 { if wf_emit(led, rid, fid, idx, "OK" as *u8, att) != 0 { return WF_EVIDENCE_ERROR }; okd = 1; att = maxa + 1 } else { att = att + 1 }
1153 }
1154 if okd == 0 {
1155 if wf_emit(led, rid, fid, idx, "FAILSTEP" as *u8, maxa) != 0 { return WF_EVIDENCE_ERROR }
1156 // R10: on-fail routing (8th field) -- exhausted ACTION failures only; the failure stays
1157 // recorded (FAILSTEP + ONFAIL), wf_error vars land on the plane, execution jumps forward
1158 let onf: *u8 = scratch.fields.onfail
1159 pipe_field(r, 7, onf, scratch.cap)
1160 var tj: i64 = 0
1161 var haveof: i64 = 0
1162 if onf[0] != (0 as u8) { haveof = 1 }
1163 if onf[0] == (45 as u8) { if onf[1] == (0 as u8) { haveof = 0 } }
1164 if haveof == 1 { tj = wf_atoi(onf) }
1165 if tj > 0 {
1166 if wf_emit(led, rid, fid, idx, "ONFAIL" as *u8, tj) != 0 { return WF_EVIDENCE_ERROR }
1167 if wf_emit_var(led, rid, "wf_error" as *u8, "yes" as *u8) != 0 { return WF_EVIDENCE_ERROR }
1168 let sv: *u8 = sys_mmap(32)
1169 var so: i64 = 0
1170 so = wf_cat(sv, so, "step" as *u8)
1171 so = wf_catn(sv, so, idx)
1172 sv[so] = 0 as u8
1173 if wf_emit_var(led, rid, "wf_error_step" as *u8, sv) != 0 { return WF_EVIDENCE_ERROR }
1174 p("WFLOW-ONFAIL run=" as *u8); p(rid); p(" step=" as *u8); pn(idx); p(" routes-to=" as *u8); pn(tj); p("\n" as *u8)
1175 jumpto = tj
1176 } else {
1177 if wf_emit(led, rid, fid, idx, "FAILED" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1178 failed = 1
1179 i = ns
1180 }
1181 } else { done = done + 1 }
1182 }
1183 }
1184 i = i + 1
1185 }
1186 if failed == 1 { return 0 - 2 }
1187 if parked == 1 { return 0 - 3 }
1188 if wf_emit(led, rid, fid, 0, "DONE" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR }
1189 return done
1190}
1191
1192// ---- fire an event: durable ledger-deduped, one run per matched flow ----
1193func wf_fire(cx:*i64,flows:*i64,nf:i64,ev:*u8)->i64{
1194 let event:*WfOwnedText=wf_id_event(ev);if event==(0 as *WfOwnedText){p("WFLOW identity ERROR invalid event; no dispatch\n");return 0-1}
1195 let sz:*i64=sys_mmap_try(__size_of(i64)) as *i64;if (sz as i64)<=0{wf_text_free(event);return WF_EVIDENCE_ERROR};let buf:*u8=wf_read_snapshot(cx[2] as *u8,sz)
1196 if buf==(0 as *u8){wf_text_free(event);sys_munmap_direct(sz as *u8,__size_of(i64));return WF_EVIDENCE_ERROR}
1197 var started:i64=0;var matched:i64=0;var f:i64=0;var rc:i64=0
1198 while f<nf{let row:*u8=flows[f] as *u8;if wf_match(row,ev)==1{matched=matched+1;let fid:*WfOwnedText=wf_id_pipe(row,0);if fid==(0 as *WfOwnedText){rc=WF_EVIDENCE_ERROR;break};let one:i64=wf_fire_pair(cx,buf,sz[0],event.data,ev,fid.data);wf_text_free(fid);if one<0{rc=one;break};started=started+one};f=f+1}
1199 wf_text_free(event);sys_munmap_direct(buf,sz[0]+1);sys_munmap_direct(sz as *u8,__size_of(i64));if rc<0{return rc};if matched>0&&started==0{return 0-100};return started
1200}
1201
1202func wf_flow_known(cx:*i64,fid:*u8)->i64{
1203 let steps:*i64=cx[0] as *i64;var i:i64=0;while i<cx[1]{let f:*WfOwnedText=wf_id_pipe(steps[i] as *u8,0);if f==(0 as *WfOwnedText){return 0-1};let same:i64=seq(f.data,fid);wf_text_free(f);if same==1{return 1};i=i+1};return 0
1204}
1205
1206func wf_resume(cx:*i64)->i64{
1207 let sz:*i64=sys_mmap_try(__size_of(i64)) as *i64;if (sz as i64)<=0{return WF_EVIDENCE_ERROR};let buf:*u8=wf_read_snapshot(cx[2] as *u8,sz);if buf==(0 as *u8){sys_munmap_direct(sz as *u8,__size_of(i64));return WF_EVIDENCE_ERROR}
1208 let n:i64=sz[0];var resumed:i64=0;var ls:i64=0;var rc:i64=0
1209 while ls<n{var le:i64=ls;while le<n{if buf[le]==(10 as u8){break};le=le+1};if le==n{rc=WF_EVIDENCE_ERROR;break}
1210 if wf_text_match(buf,ls,le,"WFRUN rid=",10)==1{
1211 let status:*WfOwnedText=wf_id_field(buf,ls,le," status=");if status==(0 as *WfOwnedText){rc=WF_EVIDENCE_ERROR;break}
1212 if seq(status.data,"START")==1{
1213 let rid:*WfOwnedText=wf_id_field(buf,ls,le," rid=");let fid:*WfOwnedText=wf_id_field(buf,ls,le," flow=")
1214 if rid==(0 as *WfOwnedText)||fid==(0 as *WfOwnedText){wf_text_free(rid);wf_text_free(fid);wf_text_free(status);rc=WF_EVIDENCE_ERROR;break}
1215 let token:*WfOwnedText=wf_id_tok(rid.data);if token==(0 as *WfOwnedText){wf_text_free(rid);wf_text_free(fid);wf_text_free(status);rc=WF_EVIDENCE_ERROR;break}
1216 if wf_lines_with2(buf,n,token.data,"status=DONE")==0&&wf_lines_with2(buf,n,token.data,"status=FAILED")==0{
1217 if wf_id_replay_binding(buf,n,rid.data,fid.data)!=1{p("WFLOW resume ERROR identity binding incomplete; no dispatch\n");rc=WF_EVIDENCE_ERROR}
1218 if rc==0{if wf_flow_known(cx,fid.data)!=1{p("WFLOW resume ERROR unknown flow=");p(fid.data);p(" -- fail loud, nothing fabricated\n");rc=0-1}}
1219 if rc==0{let ran:i64=wf_run_ver(buf,n,token.data);if cx[4]>0&&ran>0&&ran!=cx[4]{if wf_emit_drift(cx[2] as *u8,rid.data,ran,cx[4])!=0{rc=WF_EVIDENCE_ERROR}else{p("WFLOW-RESUME-WARN version drift run=");p(rid.data);p(" ran=");pn(ran);p(" now=");pn(cx[4]);p("\n")}}}
1220 if rc==0{let first:i64=wf_max_ok_step(buf,n,token.data);p("WFLOW-RESUME run=");p(rid.data);p(" from-step=");pn(first+1);p(" flow=");p(fid.data);p("\n");let run:i64=wf_run_from(cx,rid.data,fid.data,first);if run==WF_EVIDENCE_ERROR{rc=run}else{resumed=resumed+1}}
1221 };wf_text_free(token);wf_text_free(rid);wf_text_free(fid)
1222 };wf_text_free(status)
1223 };if rc!=0{break};ls=le+1
1224 };sys_munmap_direct(buf,sz[0]+1);sys_munmap_direct(sz as *u8,__size_of(i64));if rc!=0{return rc};return resumed
1225}
1226
1227func wf_lines_load(path: *u8, arr: *i64, cap: i64) -> i64 {
1228 let szp: *i64 = sys_mmap(8) as *i64
1229 let buf: *u8 = wf_read_snapshot(path, szp)
1230 if (buf as i64) == 0 { return 0 - 1 }
1231 let n: i64 = szp[0]
1232 if n == 0 { p("WFLOW files ERROR missing or empty definition file -- fail loud\n" as *u8); return 0 - 1 }
1233 var cnt: i64 = 0
1234 var ls: i64 = 0
1235 while ls < n {
1236 var le: i64 = ls
1237 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 }
1238 var keep: i64 = 0
1239 if le > ls { keep = 1 }
1240 if keep == 1 { if buf[ls] == (35 as u8) { keep = 0 } }
1241 if keep == 1 { if buf[ls] == (64 as u8) { keep = 0 } }
1242 if keep == 1 {
1243 if cnt >= cap { p("WFLOW files ERROR too many definition rows -- fail loud\n" as *u8); return 0 - 1 }
1244 let s: *u8 = sys_mmap(le - ls + 2)
1245 var k: i64 = 0
1246 while ls + k < le { s[k] = buf[ls+k]; k = k + 1 }
1247 s[k] = 0 as u8
1248 arr[cnt] = s as i64
1249 cnt = cnt + 1
1250 }
1251 ls = le + 1
1252 }
1253 return cnt
1254}
1255func wf_cx_files(flowsp: *u8, stepsp: *u8, led: *u8, cat: *u8, cx: *i64, flows: *i64) -> i64 {
1256 let steps: *i64 = sys_mmap(8 * 128) as *i64
1257 let nf: i64 = wf_lines_load(flowsp, flows, 64)
1258 if nf < 1 { return 0 - 1 }
1259 let nst: i64 = wf_lines_load(stepsp, steps, 128)
1260 if nst < 1 { return 0 - 1 }
1261 if wf_load_flows(flows, nf) < 0 { return 0 - 1 }
1262 if wf_load(steps, nst) < 0 { return 0 - 1 }
1263 cx[0] = steps as i64
1264 cx[1] = nst
1265 cx[2] = led as i64
1266 cx[3] = 0
1267 var usecat: i64 = 1
1268 if cat[0] == (45 as u8) { if cat[1] == (0 as u8) { usecat = 0 } }
1269 if usecat == 1 { cx[3] = cat as i64 }
1270 cx[4] = wf_defs_version(flowsp)
1271 return nf
1272}
1273func wf_fire_files(flowsp: *u8, stepsp: *u8, led: *u8, cat: *u8, ev: *u8) -> i64 {
1274 let cx: *i64 = sys_mmap(40) as *i64
1275 let flows: *i64 = sys_mmap(8 * 64) as *i64
1276 let nf: i64 = wf_cx_files(flowsp, stepsp, led, cat, cx, flows)
1277 if nf < 0 { return 0 - 1 }
1278 return wf_fire(cx, flows, nf, ev)
1279}
1280func wf_resume_files(flowsp: *u8, stepsp: *u8, led: *u8, cat: *u8) -> i64 {
1281 let cx: *i64 = sys_mmap(40) as *i64
1282 let flows: *i64 = sys_mmap(8 * 64) as *i64
1283 let nf: i64 = wf_cx_files(flowsp, stepsp, led, cat, cx, flows)
1284 if nf < 0 { return 0 - 1 }
1285 return wf_resume(cx)
1286}
1287
1288// ---- R6 run observability: board + 0-JS HTML derived ENTIRELY by ledger replay (nothing self-reported) ----
1289func wf_state_name(st: i64) -> *u8 {
1290 if st == 3 { return "DONE" as *u8 }
1291 if st == 2 { return "FAILED" as *u8 }
1292 if st == 1 { return "PARKED" as *u8 }
1293 return "RUNNING" as *u8
1294}
1295// derived state: DONE > FAILED > PARKED(waiting approval) > RUNNING(in-flight)
1296func wf_run_state(buf: *u8, n: i64, tok: *u8) -> i64 {
1297 if wf_lines_with2(buf, n, tok, "status=DONE" as *u8) > 0 { return 3 }
1298 if wf_lines_with2(buf, n, tok, "status=FAILED" as *u8) > 0 { return 2 }
1299 if wf_lines_with2(buf, n, tok, "status=WAIT" as *u8) > 0 { return 1 }
1300 return 0
1301}
1302// collect distinct run ids from START lines; returns count, -1 loud over cap
1303func wf_obs_runs(buf:*u8,n:i64,rids:*i64,cap:i64)->i64{
1304 var count:i64=0;var ls:i64=0
1305 while ls<n{var le:i64=ls;while le<n{if buf[le]==(10 as u8){break};le=le+1};if le==n{return 0-1}
1306 if wf_text_match(buf,ls,le,"WFRUN rid=",10)==1{let st:*WfOwnedText=wf_id_field(buf,ls,le," status=");if st==(0 as *WfOwnedText){return 0-1}
1307 if seq(st.data,"START")==1{let r:*WfOwnedText=wf_id_field(buf,ls,le," rid=");if r==(0 as *WfOwnedText){wf_text_free(st);return 0-1};var dup:i64=0;var i:i64=0;while i<count{if seq(rids[i] as *u8,r.data)==1{dup=1};i=i+1}
1308 if dup==0{if count>=cap{wf_text_free(r);wf_text_free(st);return 0-1};rids[count]=r.data as i64;count=count+1;sys_munmap_direct(r as *u8,__size_of(WfOwnedText))}else{wf_text_free(r)}
1309 };wf_text_free(st)
1310 };ls=le+1
1311 };return count
1312}
1313
1314func wf_obs_tok(tok: *u8, rid: *u8) -> i64 {
1315 var q: i64 = 0
1316 q = wf_cat(tok, q, " rid=" as *u8)
1317 q = wf_cat(tok, q, rid)
1318 tok[q] = 32 as u8
1319 tok[q+1] = 0 as u8
1320 return q
1321}
1322func wf_obs_release(buf:*u8,n:i64,rids:*i64,slots:i64)->i64{
1323 if (rids as i64)>0{var i:i64=0;while i<slots{let rid:*u8=rids[i] as *u8;if (rid as i64)>0{sys_munmap_direct(rid,slen(rid)+1)};i=i+1};sys_munmap_direct(rids as *u8,slots*__size_of(i64))}
1324 if (buf as i64)>0{sys_munmap_direct(buf,n+1)};return 0
1325}
1326func wf_obs_board(led: *u8) -> i64 {
1327 let szp: *i64 = sys_mmap_try(__size_of(i64)) as *i64
1328 if (szp as i64)<=0{return WF_EVIDENCE_ERROR}
1329 let buf: *u8 = wf_read_snapshot(led, szp)
1330 if (buf as i64) == 0 { sys_munmap_direct(szp as *u8,8); return 0 - 1 }
1331 let n: i64 = szp[0]
1332 sys_munmap_direct(szp as *u8,8)
1333 let slots:i64=n/slen("WFRUN rid=")+1
1334 let rids:*i64=sys_mmap_try(slots*__size_of(i64)) as *i64
1335 if (rids as i64)<=0{wf_obs_release(buf,n,0 as *i64,0);return 0-1}
1336 let cnt:i64=wf_obs_runs(buf,n,rids,slots)
1337 if cnt < 0 { wf_obs_release(buf,n,rids,slots);return 0 - 1 }
1338 let tok:*u8=sys_mmap_try(n+1)
1339 if (tok as i64)<=0{wf_obs_release(buf,n,rids,slots);return 0-1}
1340 var d: i64 = 0
1341 var f: i64 = 0
1342 var w: i64 = 0
1343 var ru: i64 = 0
1344 var i: i64 = 0
1345 while i < cnt {
1346 let rid: *u8 = rids[i] as *u8
1347 wf_obs_tok(tok, rid)
1348 let st: i64 = wf_run_state(buf, n, tok)
1349 let oks: i64 = wf_lines_with2(buf, n, tok, "status=OK" as *u8)
1350 let ats: i64 = wf_lines_with2(buf, n, tok, "status=ATT" as *u8)
1351 p("WFOBS run=" as *u8); p(rid)
1352 p(" state=" as *u8); p(wf_state_name(st))
1353 p(" oksteps=" as *u8); pn(oks)
1354 p(" attempts=" as *u8); pn(ats)
1355 p("\n" as *u8)
1356 if st == 3 { d = d + 1 }
1357 if st == 2 { f = f + 1 }
1358 if st == 1 { w = w + 1 }
1359 if st == 0 { ru = ru + 1 }
1360 i = i + 1
1361 }
1362 p("WFOBS-TOTALS runs=" as *u8); pn(cnt); p(" done=" as *u8); pn(d); p(" failed=" as *u8); pn(f); p(" parked=" as *u8); pn(w); p(" running=" as *u8); pn(ru); p("\n" as *u8)
1363 sys_munmap_direct(tok,n+1);wf_obs_release(buf,n,rids,slots)
1364 return cnt
1365}
1366// 0-JS sovereign run board page; unquoted HTML5 attrs + rgb() colors (no quote/hash/bang literals)
1367func wf_obs_html(led: *u8, out: *u8) -> i64 {
1368 let szp: *i64 = sys_mmap_try(__size_of(i64)) as *i64
1369 if (szp as i64)<=0{return WF_EVIDENCE_ERROR}
1370 let buf: *u8 = wf_read_snapshot(led, szp)
1371 if (buf as i64) == 0 { sys_munmap_direct(szp as *u8,8); return 0 - 1 }
1372 let n: i64 = szp[0]
1373 sys_munmap_direct(szp as *u8,8)
1374 let slots:i64=n/slen("WFRUN rid=")+1
1375 let rids:*i64=sys_mmap_try(slots*__size_of(i64)) as *i64
1376 if (rids as i64)<=0{wf_obs_release(buf,n,0 as *i64,0);return 0-1}
1377 let cnt:i64=wf_obs_runs(buf,n,rids,slots)
1378 if cnt < 0 { wf_obs_release(buf,n,rids,slots);return 0 - 1 }
1379 var needed:i64=slen("<")+slen("DOCTYPE html><html><head><meta charset=utf-8><title>Nishi Workflow Runs</title><style>body{font-family:monospace;background:rgb(16,17,22);color:rgb(222,224,230);margin:2em}table{border-collapse:collapse}td,th{border:1px solid rgb(60,62,72);padding:6px 12px}h1{color:rgb(140,190,255)}.DONE{color:rgb(90,210,130)}.FAILED{color:rgb(245,95,95)}.PARKED{color:rgb(235,195,95)}.RUNNING{color:rgb(120,175,245)}</style></head><body><h1>Nishi Workflow Runs</h1><p>Derived by REPLAY of the event-sourced run ledger, nothing self-reported.</p><table><tr><th>run</th><th>state</th><th>ok steps</th><th>attempts</th></tr>")+slen("<tr><td>")+slen("</td><td class=")+slen(">")+slen("</td><td>")+slen("</td><td>")+slen("</td></tr>")+slen("</table><p>runs ")+slen(" · done ")+slen(" · failed ")+slen(" · parked ")+slen(" · running ")+slen("</p><p>Generated by nx_wflow_engine wf_obs_html (sovereign, 0-JS, ledger replay).</p></body></html>")+2+slen("9223372036854775807")*5
1380 var measure:i64=0
1381 while measure<cnt{let escaped:*WfOwnedText=wf_id_html(rids[measure] as *u8);if escaped==(0 as *WfOwnedText){wf_obs_release(buf,n,rids,slots);return 0-1};let extra:i64=escaped.len+slen("<tr><td></td><td class=></td><td></td><td></td></tr>")+slen("RUNNING")*2+slen("9223372036854775807")*2;if extra>WF_TEXT_I64_MAX-needed{wf_text_free(escaped);wf_obs_release(buf,n,rids,slots);return 0-1};needed=needed+extra;wf_text_free(escaped);measure=measure+1}
1382 let hb:*u8=sys_mmap_try(needed)
1383 if (hb as i64)<=0{wf_obs_release(buf,n,rids,slots);return 0-1}
1384 var o: i64 = 0
1385 o = wf_cat(hb, o, "<" as *u8)
1386 hb[o] = 33 as u8
1387 o = o + 1
1388 o = wf_cat(hb, o, "DOCTYPE html><html><head><meta charset=utf-8><title>Nishi Workflow Runs</title><style>body{font-family:monospace;background:rgb(16,17,22);color:rgb(222,224,230);margin:2em}table{border-collapse:collapse}td,th{border:1px solid rgb(60,62,72);padding:6px 12px}h1{color:rgb(140,190,255)}.DONE{color:rgb(90,210,130)}.FAILED{color:rgb(245,95,95)}.PARKED{color:rgb(235,195,95)}.RUNNING{color:rgb(120,175,245)}</style></head><body><h1>Nishi Workflow Runs</h1><p>Derived by REPLAY of the event-sourced run ledger, nothing self-reported.</p><table><tr><th>run</th><th>state</th><th>ok steps</th><th>attempts</th></tr>" as *u8)
1389 let tok:*u8=sys_mmap_try(n+1)
1390 if (tok as i64)<=0{sys_munmap_direct(hb,needed);wf_obs_release(buf,n,rids,slots);return 0-1}
1391 var d: i64 = 0
1392 var f: i64 = 0
1393 var w: i64 = 0
1394 var ru: i64 = 0
1395 var i: i64 = 0
1396 while i < cnt {
1397 let rid: *u8 = rids[i] as *u8
1398 wf_obs_tok(tok, rid)
1399 let st: i64 = wf_run_state(buf, n, tok)
1400 let oks: i64 = wf_lines_with2(buf, n, tok, "status=OK" as *u8)
1401 let ats: i64 = wf_lines_with2(buf, n, tok, "status=ATT" as *u8)
1402 o = wf_cat(hb, o, "<tr><td>" as *u8)
1403 let escaped:*WfOwnedText=wf_id_html(rid)
1404 if escaped==(0 as *WfOwnedText){sys_munmap_direct(tok,n+1);sys_munmap_direct(hb,needed);wf_obs_release(buf,n,rids,slots);return 0-1}
1405 o=wf_cat(hb,o,escaped.data);wf_text_free(escaped)
1406 o = wf_cat(hb, o, "</td><td class=" as *u8)
1407 o = wf_cat(hb, o, wf_state_name(st))
1408 o = wf_cat(hb, o, ">" as *u8)
1409 o = wf_cat(hb, o, wf_state_name(st))
1410 o = wf_cat(hb, o, "</td><td>" as *u8)
1411 o = wf_catn(hb, o, oks)
1412 o = wf_cat(hb, o, "</td><td>" as *u8)
1413 o = wf_catn(hb, o, ats)
1414 o = wf_cat(hb, o, "</td></tr>" as *u8)
1415 if st == 3 { d = d + 1 }
1416 if st == 2 { f = f + 1 }
1417 if st == 1 { w = w + 1 }
1418 if st == 0 { ru = ru + 1 }
1419 i = i + 1
1420 }
1421 o = wf_cat(hb, o, "</table><p>runs " as *u8)
1422 o = wf_catn(hb, o, cnt)
1423 o = wf_cat(hb, o, " · done " as *u8)
1424 o = wf_catn(hb, o, d)
1425 o = wf_cat(hb, o, " · failed " as *u8)
1426 o = wf_catn(hb, o, f)
1427 o = wf_cat(hb, o, " · parked " as *u8)
1428 o = wf_catn(hb, o, w)
1429 o = wf_cat(hb, o, " · running " as *u8)
1430 o = wf_catn(hb, o, ru)
1431 o = wf_cat(hb, o, "</p><p>Generated by nx_wflow_engine wf_obs_html (sovereign, 0-JS, ledger replay).</p></body></html>" as *u8)
1432 let write_rc:i64=wf_writeall(out,hb,o)
1433 sys_munmap_direct(tok,n+1);sys_munmap_direct(hb,needed);wf_obs_release(buf,n,rids,slots)
1434 if write_rc!=0{return 0-1};return cnt
1435}
1436
1437func wf_selftest() -> i64 {
1438 p("=== NX-WFLOW-ENGINE SELFTEST (R1 keystone: multi-step durable runs, retry, ledger-dedup, crash-resume) ===\n" as *u8)
1439 var ok: i64 = 1
1440
1441 // fresh unique ledger under /tmp (getpid is broken on this backend -- use epoch seconds)
1442 let led: *u8 = sys_mmap(128)
1443 var lo: i64 = 0
1444 lo = wf_cat(led, lo, "/tmp/wflow_st_" as *u8)
1445 lo = wf_catn(led, lo, wf_now())
1446 lo = wf_cat(led, lo, ".log" as *u8)
1447 led[lo] = 0 as u8
1448
1449 let flows: *i64 = sys_mmap(8 * 8) as *i64
1450 flows[0] = "f1|deal-stage|to=Negotiation" as *u8 as i64
1451 flows[1] = "f2|giving-received|-" as *u8 as i64
1452 flows[2] = "f3|ops-check|-" as *u8 as i64
1453 let steps: *i64 = sys_mmap(8 * 16) as *i64
1454 steps[0] = "f1|1|create-task|-|Prepare contract and pricing|1" as *u8 as i64
1455 steps[1] = "f1|2|send|card|Congrats on reaching Negotiation|2" as *u8 as i64
1456 steps[2] = "f1|3|notify|-|Deal advanced|1" as *u8 as i64
1457 steps[3] = "f2|1|probe-fail|-|3|4" as *u8 as i64
1458 steps[4] = "f2|2|update-field|-|status=thanked|1" as *u8 as i64
1459 steps[5] = "f3|1|probe-fail|-|9|2" as *u8 as i64
1460 steps[6] = "f3|2|notify|-|never reached|1" as *u8 as i64
1461
1462 let cx: *i64 = sys_mmap(32) as *i64
1463 cx[0] = steps as i64
1464 cx[1] = 7
1465 cx[2] = led as i64
1466 cx[3] = 0
1467
1468 // T1 whole-set validation, loud refusals
1469 let l1: i64 = wf_load(steps, 7)
1470 let l2: i64 = wf_load_flows(flows, 3)
1471 p(" T1 load steps=" as *u8); pn(l1); p(" flows=" as *u8); pn(l2); p("\n" as *u8)
1472 if l1 != 7 { ok = 0 }
1473 if l2 != 3 { ok = 0 }
1474 let bad1: *i64 = sys_mmap(16) as *i64
1475 bad1[0] = "f9|1|explode|-|boom|1" as *u8 as i64
1476 if wf_load(bad1, 1) != (0 - 1) { ok = 0 }
1477 let bad2: *i64 = sys_mmap(16) as *i64
1478 bad2[0] = "f9|1|send|fax|x|1" as *u8 as i64
1479 if wf_load(bad2, 1) != (0 - 1) { ok = 0 }
1480 let bad3: *i64 = sys_mmap(16) as *i64
1481 bad3[0] = "f9|1|notify|-|x|0" as *u8 as i64
1482 if wf_load(bad3, 1) != (0 - 1) { ok = 0 }
1483
1484 let ridE1:*WfOwnedText=wf_id_hex("E1","f1");let tokE1:*WfOwnedText=wf_id_tok(ridE1.data)
1485 let ridE2:*WfOwnedText=wf_id_hex("E2","f2");let tokE2:*WfOwnedText=wf_id_tok(ridE2.data)
1486 let ridE3:*WfOwnedText=wf_id_hex("E3","f3");let tokE3:*WfOwnedText=wf_id_tok(ridE3.data)
1487 // T2 fire -> 3-step run completes in order, DONE recorded
1488 let r1: i64 = wf_fire(cx, flows, 3, "deal-stage~deal=Riverside Contract~to=Negotiation~id=E1" as *u8)
1489 let szp: *i64 = sys_mmap(8) as *i64
1490 var buf: *u8 = wf_read_snapshot(led, szp)
1491 let okE1: i64 = wf_lines_with2(buf, szp[0], tokE1.data, "status=OK" as *u8)
1492 let dnE1: i64 = wf_lines_with2(buf, szp[0], tokE1.data, "status=DONE" as *u8)
1493 p(" T2 fire started=" as *u8); pn(r1); p(" okSteps=" as *u8); pn(okE1); p(" done=" as *u8); pn(dnE1); p("\n" as *u8)
1494 if r1 != 1 { ok = 0 }
1495 if okE1 != 3 { ok = 0 }
1496 if dnE1 != 1 { ok = 0 }
1497
1498 // T3 durable dedup: same event id never starts twice (survives across processes via the ledger)
1499 let r2: i64 = wf_fire(cx, flows, 3, "deal-stage~deal=Riverside Contract~to=Negotiation~id=E1" as *u8)
1500 buf = wf_read_snapshot(led, szp)
1501 let stE1: i64 = wf_lines_with2(buf, szp[0], tokE1.data, "status=START" as *u8)
1502 p(" T3 refire rc=" as *u8); pn(r2); p(" starts=" as *u8); pn(stE1); p("\n" as *u8)
1503 if r2 != (0 - 100) { ok = 0 }
1504 if stE1 != 1 { ok = 0 }
1505
1506 // T4 per-step retry: probe-fail 3 with max 4 succeeds on attempt 3
1507 let r3: i64 = wf_fire(cx, flows, 3, "giving-received~person=Rose~amount=500~id=E2" as *u8)
1508 buf = wf_read_snapshot(led, szp)
1509 let ok3: i64 = wf_lines_with2(buf, szp[0], tokE2.data, "status=OK att=3" as *u8)
1510 let atts: i64 = wf_lines_with2(buf, szp[0], tokE2.data, "status=ATT" as *u8)
1511 let dnE2: i64 = wf_lines_with2(buf, szp[0], tokE2.data, "status=DONE" as *u8)
1512 p(" T4 retry started=" as *u8); pn(r3); p(" okAtt3=" as *u8); pn(ok3); p(" attempts=" as *u8); pn(atts); p(" done=" as *u8); pn(dnE2); p("\n" as *u8)
1513 if r3 != 1 { ok = 0 }
1514 if ok3 != 1 { ok = 0 }
1515 if atts != 4 { ok = 0 }
1516 if dnE2 != 1 { ok = 0 }
1517
1518 // T5 exhausted retries -> FAILED at step, later steps never run
1519 let r4: i64 = wf_fire(cx, flows, 3, "ops-check~probe=edge~id=E3" as *u8)
1520 buf = wf_read_snapshot(led, szp)
1521 let fsE3: i64 = wf_lines_with2(buf, szp[0], tokE3.data, "status=FAILSTEP" as *u8)
1522 let flE3: i64 = wf_lines_with2(buf, szp[0], tokE3.data, "status=FAILED" as *u8)
1523 let s2E3: i64 = wf_lines_with2(buf, szp[0], tokE3.data, " step=2 " as *u8)
1524 let dnE3: i64 = wf_lines_with2(buf, szp[0], tokE3.data, "status=DONE" as *u8)
1525 p(" T5 exhaust rc=" as *u8); pn(r4); p(" failstep=" as *u8); pn(fsE3); p(" failed=" as *u8); pn(flE3); p(" step2lines=" as *u8); pn(s2E3); p(" done=" as *u8); pn(dnE3); p("\n" as *u8)
1526 if fsE3 != 1 { ok = 0 }
1527 if flE3 != 1 { ok = 0 }
1528 if s2E3 != 0 { ok = 0 }
1529 if dnE3 != 0 { ok = 0 }
1530
1531 // T6 THE CROWN -- durable crash-resume: synthetic run crashed after step 1; resume completes 2..3
1532 // WITHOUT re-executing step 1 (event-sourced replay, additive only)
1533 wf_emit(led, "E9.f1" as *u8, "f1" as *u8, 0, "START" as *u8, 0)
1534 wf_emit_var(led,"E9.f1","id","E9")
1535 wf_emit(led, "E9.f1" as *u8, "f1" as *u8, 1, "ATT" as *u8, 1)
1536 wf_emit(led, "E9.f1" as *u8, "f1" as *u8, 1, "OK" as *u8, 1)
1537 let rs: i64 = wf_resume(cx)
1538 buf = wf_read_snapshot(led, szp)
1539 let okE9: i64 = wf_lines_with2(buf, szp[0], " rid=E9.f1 " as *u8, "status=OK" as *u8)
1540 let ok1E9: i64 = wf_lines_with2(buf, szp[0], " rid=E9.f1 " as *u8, " step=1 status=OK" as *u8)
1541 let dnE9: i64 = wf_lines_with2(buf, szp[0], " rid=E9.f1 " as *u8, "status=DONE" as *u8)
1542 p(" T6 resume resumed=" as *u8); pn(rs); p(" okSteps=" as *u8); pn(okE9); p(" step1okOnce=" as *u8); pn(ok1E9); p(" done=" as *u8); pn(dnE9); p("\n" as *u8)
1543 if rs != 1 { ok = 0 }
1544 if okE9 != 3 { ok = 0 }
1545 if ok1E9 != 1 { ok = 0 }
1546 if dnE9 != 1 { ok = 0 }
1547
1548 // T7 liar-kill negatives: unknown-flow in-flight run refuses loudly (no fabricated DONE);
1549 // nonsense status never appears
1550 wf_emit(led, "EX.zz" as *u8, "zz" as *u8, 0, "START" as *u8, 0)
1551 wf_emit_var(led,"EX.zz","id","EX")
1552 let rz: i64 = wf_resume(cx)
1553 buf = wf_read_snapshot(led, szp)
1554 let dnZZ: i64 = wf_lines_with2(buf, szp[0], " rid=EX.zz " as *u8, "status=DONE" as *u8)
1555 let ban: i64 = wf_lines_with2(buf, szp[0], "WFRUN" as *u8, "status=BANANA" as *u8)
1556 p(" T7 negctl resumeRc=" as *u8); pn(rz); p(" zzDone=" as *u8); pn(dnZZ); p(" nonsense=" as *u8); pn(ban); p("\n" as *u8)
1557 if rz != (0 - 1) { ok = 0 }
1558 if dnZZ != 0 { ok = 0 }
1559 if ban != 0 { ok = 0 }
1560
1561 p("NX-WFLOW-ENGINE-SELFTEST ledger=" as *u8); p(led)
1562 p(" runs: done=3 failed=1 dedup=1 resumed=1 " as *u8)
1563 if ok == 1 { p("verdict=GREEN\n" as *u8); return 0 }
1564 p("verdict=RED\n" as *u8)
1565 return 1
1566}