code wiki / (root) / nx_wflow_ownership_old_engine_fixture_t344.nx

nx_wflow_ownership_old_engine_fixture_t344.nx source

↩ module page · 1548 lines · 72559 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_ownership_old_text_fixture_t344.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 ln: *u8 = wf_id_alloc(rid,fid,st,"",slen("WFRUN rid= flow= step= status= att=\n")+slen("9223372036854775807")*2+1) 381 if ln==(0 as *u8){return WF_EVIDENCE_ERROR} 382 var o: i64 = 0 383 o = wf_cat(ln, o, "WFRUN rid=" as *u8) 384 o = wf_cat(ln, o, rid) 385 o = wf_cat(ln, o, " flow=" as *u8) 386 o = wf_cat(ln, o, fid) 387 o = wf_cat(ln, o, " step=" as *u8) 388 o = wf_catn(ln, o, step) 389 o = wf_cat(ln, o, " status=" as *u8) 390 o = wf_cat(ln, o, st) 391 o = wf_cat(ln, o, " att=" as *u8) 392 o = wf_catn(ln, o, att) 393 ln[o] = 10 as u8 394 ln[o+1] = 0 as u8 395 let rc:i64=wf_append(led,ln) 396 sys_munmap_direct(ln,slen(ln)+1) 397 return rc 398} 399 400// ---- R3 connector layer: GREEN fail-closed catalog, any blessed organ as a workflow step ---- 401// catalog rows: name<TAB>elfpath<TAB>GREEN. No catalog / unknown name / flag not exactly GREEN = refuse. 402func wf_conn_resolve(cx: *i64, name: *u8, out: *u8) -> i64 { 403 let conf: *u8 = cx[3] as *u8 404 if (conf as i64) == 0 { p("WFLOW exec-organ ERROR no connector catalog loaded -- fail closed\n" as *u8); return 0 } 405 let szp: *i64 = sys_mmap(8) as *i64 406 let buf: *u8 = wf_read_snapshot(conf, szp) 407 if (buf as i64) == 0 { return 0 } 408 let n: i64 = szp[0] 409 if n == 0 { p("WFLOW exec-organ ERROR connector catalog missing or empty -- fail closed\n" as *u8); return 0 } 410 let nm: i64 = slen(name) 411 var ls: i64 = 0 412 while ls < n { 413 var le: i64 = ls 414 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 } 415 var t1: i64 = ls 416 while t1 < le { if buf[t1] == (9 as u8) { break } t1 = t1 + 1 } 417 var hit: i64 = 0 418 if t1 - ls == nm { 419 hit = 1 420 var k: i64 = 0 421 while k < nm { if buf[ls+k] != name[k] { hit = 0; k = nm } else { k = k + 1 } } 422 } 423 if hit == 1 { 424 var t2: i64 = t1 + 1 425 while t2 < le { if buf[t2] == (9 as u8) { break } t2 = t2 + 1 } 426 let fl0: i64 = t2 + 1 427 var okflag: i64 = 0 428 if le - fl0 == 5 { 429 okflag = 1 430 let g: *u8 = "GREEN" as *u8 431 var k2: i64 = 0 432 while k2 < 5 { if buf[fl0+k2] != g[k2] { okflag = 0; k2 = 5 } else { k2 = k2 + 1 } } 433 } 434 if okflag == 0 { p("WFLOW exec-organ ERROR connector=" as *u8); p(name); p(" not GREEN -- fail closed\n" as *u8); return 0 } 435 var q: i64 = t1 + 1 436 var o: i64 = 0 437 while q < t2 { if o < 500 { out[o] = buf[q]; o = o + 1 } q = q + 1 } 438 out[o] = 0 as u8 439 if o == 0 { return 0 } 440 return 1 441 } 442 ls = le + 1 443 } 444 p("WFLOW exec-organ ERROR connector unknown=" as *u8); p(name); p(" -- fail closed\n" as *u8) 445 return 0 446} 447func wf_rd32(b: *u8, off: i64) -> i64 { 448 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) 449} 450// run the resolved organ as the step: argv = arg split on ~ ('-' or empty = no args); exit 0 = step OK. 451// R8b: channel `connector>varname` CAPTURES child stdout (bounded 480B, newlines to spaces, loud over-cap) 452// into the run variable plane -- step OUTPUT becomes later steps' INPUT. 453func wf_exec_organ(cx: *i64, name0: *u8, arg: *u8, rid: *u8) -> i64 { 454 let name: *u8 = sys_mmap(128) 455 let capv: *u8 = sys_mmap(128) 456 var ni: i64 = 0 457 var ci: i64 = 0 458 var seen: i64 = 0 459 var i0: i64 = 0 460 while name0[i0] != (0 as u8) { 461 if name0[i0] == (62 as u8) { seen = 1 } else { 462 if seen == 0 { if ni < 126 { name[ni] = name0[i0]; ni = ni + 1 } } else { if ci < 126 { capv[ci] = name0[i0]; ci = ci + 1 } } 463 } 464 i0 = i0 + 1 465 } 466 name[ni] = 0 as u8 467 capv[ci] = 0 as u8 468 var docap: i64 = 0 469 if ci > 0 { docap = 1 } 470 let elf: *u8 = sys_mmap(512) 471 if wf_conn_resolve(cx, name, elf) == 0 { return 0 } 472 let av: *i64 = sys_mmap(8 * 16) as *i64 473 av[0] = elf as i64 474 var na: i64 = 1 475 var skip: i64 = 0 476 if arg[0] == (0 as u8) { skip = 1 } 477 if arg[0] == (45 as u8) { if arg[1] == (0 as u8) { skip = 1 } } 478 if skip == 0 { 479 let ab: *u8 = sys_mmap(WF_MAGIC_1024) 480 var i: i64 = 0 481 var over: i64 = 0 482 while arg[i] != (0 as u8) { if i < 1000 { ab[i] = arg[i] } else { over = 1 } i = i + 1 } 483 if over == 1 { p("WFLOW exec-organ ERROR argv too long -- fail closed\n" as *u8); return 0 } 484 ab[i] = 0 as u8 485 let alen: i64 = i 486 var st0: i64 = 0 487 var j: i64 = 0 488 while j <= alen { 489 var cut: i64 = 0 490 if j == alen { cut = 1 } 491 if cut == 0 { if ab[j] == (126 as u8) { cut = 1 } } 492 if cut == 1 { 493 ab[j] = 0 as u8 494 if na >= 14 { p("WFLOW exec-organ ERROR too many args -- fail closed\n" as *u8); return 0 } 495 av[na] = (ab as i64) + st0 496 na = na + 1 497 st0 = j + 1 498 } 499 j = j + 1 500 } 501 } 502 av[na] = 0 503 let fb: *u8 = sys_mmap(16) 504 if docap == 1 { 505 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 } 506 } 507 let pid: i64 = sys_fork() 508 if pid == 0 { 509 if docap == 1 { 510 let pw: i64 = wf_rd32(fb, 4) 511 sys_dup3(pw, 1, 0) 512 __syscall(3, wf_rd32(fb, 0), 0, 0, 0, 0, 0) 513 __syscall(3, pw, 0, 0, 0, 0, 0) 514 } 515 let envp: *i64 = sys_mmap(16) as *i64 516 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64 517 envp[1] = 0 518 sys_execve(elf, av, envp) 519 sys_exit(127) 520 } 521 if pid < 0 { p("WFLOW exec-organ ERROR fork failed -- fail closed\n" as *u8); return 0 } 522 let cbuf: *u8 = sys_mmap(WF_MAGIC_1024) 523 var total: i64 = 0 524 if docap == 1 { 525 let pr: i64 = wf_rd32(fb, 0) 526 __syscall(3, wf_rd32(fb, 4), 0, 0, 0, 0, 0) 527 var go: i64 = 1 528 while go == 1 { 529 let rr: i64 = __syscall(0, pr, (cbuf as i64) + total, 1023 - total, 0, 0, 0) 530 if rr <= 0 { go = 0 } 531 if go == 1 { total = total + rr; if total >= 1023 { go = 0 } } 532 } 533 __syscall(3, pr, 0, 0, 0, 0, 0) 534 } 535 let st: *i64 = sys_mmap(16) as *i64 536 var waited: i64 = sys_wait4(pid, st, 0) 537 while waited == (0 - 4) { waited = sys_wait4(pid, st, 0) } 538 if waited != pid { p("WFLOW exec-organ ERROR child wait failed -- fail closed\n" as *u8); return 0 } 539 let ec: i64 = wait_status_rc(st[0]) 540 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) 541 if ec != 0 { return 0 } 542 if docap == 1 { 543 if total > 480 { p("WFLOW capture ERROR output over 480 bytes -- fail loud\n" as *u8); return 0 } 544 var t: i64 = 0 545 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 } 546 while total > 0 { if cbuf[total-1] == (32 as u8) { total = total - 1 } else { break } } 547 cbuf[total] = 0 as u8 548 let ledc: *u8 = cx[2] as *u8 549 if wf_emit_var(ledc, rid, capv, cbuf) != 0 { return WF_EVIDENCE_ERROR } 550 p("WFLOW-CAPTURE run=" as *u8); p(rid); p(" var=" as *u8); p(capv); p(" v=" as *u8); p(cbuf); p("\n" as *u8) 551 } 552 return 1 553} 554 555// ---- R11 loops: apply-to-each over the connector plane ---- 556func wf_emit_iter(led: *u8, rid: *u8, n: i64, val: *u8) -> i64 { 557 let number:*WfOwnedText=wf_text_decimal(n) 558 if number==(0 as *WfOwnedText){return WF_EVIDENCE_ERROR} 559 let rn:i64=slen(rid);let vn:i64=slen(val) 560 var bytes:i64=slen("WFITER rid=")+slen(" n=")+slen(" item=")+2 561 if number.len>WF_TEXT_I64_MAX-bytes{wf_text_free(number);return WF_EVIDENCE_ERROR};bytes=bytes+number.len 562 if rn>WF_TEXT_I64_MAX-bytes{wf_text_free(number);return WF_EVIDENCE_ERROR};bytes=bytes+rn 563 if vn>WF_TEXT_I64_MAX-bytes{wf_text_free(number);return WF_EVIDENCE_ERROR};bytes=bytes+vn 564 let line:*u8=sys_mmap_try(bytes) 565 if (line as i64)<=0{wf_text_free(number);return WF_EVIDENCE_ERROR} 566 var at:i64=wf_cat(line,0,"WFITER rid=") 567 at=wf_cat(line,at,rid);at=wf_cat(line,at," n=");at=wf_cat(line,at,number.data) 568 at=wf_cat(line,at," item=");at=wf_cat(line,at,val);line[at]=10 as u8;line[at+1]=0 569 var rc:i64=wf_append(led,line) 570 if sys_munmap_direct(line,bytes)!=0{rc=WF_EVIDENCE_ERROR} 571 if wf_text_free(number)!=0{rc=WF_EVIDENCE_ERROR} 572 return rc 573} 574// for-each: arg = <listexpr>~<per-item template> (template default {item}). The list substitutes ONCE 575// (comma-split, empties skipped; input-sized storage); per item: bind {item} as a run var, emit WFITER, 576// substitute the template, fork the connector. Any failed item fails the STEP (retry/on-fail apply). 577// Empty list = zero iterations, step OK (apply-to-each on an empty collection is a no-op). 578func wf_for_each(cx: *i64, chspec: *u8, argraw: *u8, rid: *u8) -> i64 { 579 let led:*u8=cx[2] as *u8;let total:i64=slen(argraw) 580 var sep:i64=0;while sep<total{if argraw[sep]==(126 as u8){break};sep=sep+1} 581 let list:*WfOwnedText=wf_text_slice(argraw,0,sep) 582 if list==(0 as *WfOwnedText){return WF_EVIDENCE_ERROR} 583 var tpl:*WfOwnedText=0 as *WfOwnedText 584 if sep<total{tpl=wf_text_slice(argraw,sep+1,total-sep-1)}else{tpl=wf_text_slice("{item}",0,slen("{item}"))} 585 if tpl==(0 as *WfOwnedText){wf_text_free(list);return WF_EVIDENCE_ERROR} 586 let expanded:*WfOwnedText=wf_subst_owned(cx,rid,list.data) 587 wf_text_free(list) 588 if expanded==(0 as *WfOwnedText){wf_text_free(tpl);return 0} 589 var rc:i64=1;var start:i64=0;var cursor:i64=0;var item:i64=0 590 while cursor<=expanded.len{ 591 var cut:i64=0 592 if cursor==expanded.len{cut=1}else{if expanded.data[cursor]==(44 as u8){cut=1}} 593 if cut==1{ 594 expanded.data[cursor]=0 595 if cursor>start{ 596 let value:*u8=((expanded.data as i64)+start) as *u8;item=item+1 597 if wf_emit_var(led,rid,"item",value)!=0{rc=WF_EVIDENCE_ERROR;break} 598 if wf_emit_iter(led,rid,item,value)!=0{rc=WF_EVIDENCE_ERROR;break} 599 let prepared:*WfOwnedText=wf_subst_owned(cx,rid,tpl.data) 600 if prepared==(0 as *WfOwnedText){rc=0;break} 601 let result:i64=wf_exec_organ(cx,chspec,prepared.data,rid) 602 if wf_text_free(prepared)!=0{rc=WF_EVIDENCE_ERROR;break} 603 if result!=1{rc=result;break} 604 } 605 start=cursor+1 606 } 607 cursor=cursor+1 608 } 609 if wf_text_free(expanded)!=0{rc=WF_EVIDENCE_ERROR} 610 if wf_text_free(tpl)!=0{rc=WF_EVIDENCE_ERROR} 611 return rc 612} 613 614// ---- action execution (in-process v1 + exec-organ; send-channel semantics from nx_send) ---- 615func wf_exec(cx: *i64, action: *u8, ch: *u8, arg: *u8, att: i64, rid: *u8) -> i64 { 616 p("WFLOW-ACTION action=" as *u8); p(action) 617 if seq(action, "send" as *u8) == 1 { p(" channel=" as *u8); p(ch); p(" route=" as *u8); p(sr_route(ch)) } 618 p(" arg=" as *u8); p(arg) 619 p(" att=" as *u8); pn(att) 620 p("\n" as *u8) 621 if seq(action, "probe-fail" as *u8) == 1 { 622 let k: i64 = wf_atoi(arg) 623 if att < k { return 0 } 624 return 1 625 } 626 if seq(action, "exec-organ" as *u8) == 1 { return wf_exec_organ(cx, ch, arg, rid) } 627 if seq(action, "for-each" as *u8) == 1 { return wf_for_each(cx, ch, arg, rid) } 628 return 1 629} 630 631// ---- R4 human-in-the-loop approvals: WFDEC decision records on the same ledger ---- 632// WFDEC rid=<rid> step=<n> decision=APPROVED|DENIED by=<who>. DENY WINS if both exist (fail-safe, 633// deny-by-default). No decision = the approve step PARKS the run (status=WAIT, in-flight); wf_resume 634// re-examines it and completes once a decision lands. 635func wf_decision(cx: *i64, rid: *u8, idx: i64) -> i64 { 636 let led: *u8 = cx[2] as *u8 637 let szp: *i64 = sys_mmap(8) as *i64 638 let buf: *u8 = wf_read_snapshot(led, szp) 639 if (buf as i64) == 0 { return 0 } 640 let token:*WfOwnedText=wf_id_tok(rid) 641 if token==(0 as *WfOwnedText){return WF_EVIDENCE_ERROR} 642 let tok:*u8=token.data 643 var q: i64 = 0 644 q = wf_cat(tok, q, " rid=" as *u8) 645 q = wf_cat(tok, q, rid) 646 tok[q] = 32 as u8 647 tok[q+1] = 0 as u8 648 let nb: *u8 = sys_mmap(128) 649 var o: i64 = 0 650 o = wf_cat(nb, o, " step=" as *u8) 651 o = wf_catn(nb, o, idx) 652 o = wf_cat(nb, o, " decision=DENIED" as *u8) 653 nb[o] = 0 as u8 654 if wf_lines_with2(buf, szp[0], tok, nb) > 0 { return 0 - 1 } 655 var o2: i64 = 0 656 o2 = wf_cat(nb, o2, " step=" as *u8) 657 o2 = wf_catn(nb, o2, idx) 658 o2 = wf_cat(nb, o2, " decision=APPROVED" as *u8) 659 nb[o2] = 0 as u8 660 if wf_lines_with2(buf, szp[0], tok, nb) > 0 { return 1 } 661 return 0 662} 663// the operator surface: append a decision record (only APPROVED or DENIED accepted, loud else) 664func wf_decide(cx: *i64, rid: *u8, idx: i64, decision: *u8, who: *u8) -> i64 { 665 var okd: i64 = 0 666 if seq(decision, "APPROVED" as *u8) == 1 { okd = 1 } 667 if seq(decision, "DENIED" as *u8) == 1 { okd = 1 } 668 if okd == 0 { p("WFLOW decide ERROR decision must be APPROVED or DENIED -- fail loud\n" as *u8); return 0 - 1 } 669 let led: *u8 = cx[2] as *u8 670 let ln: *u8 = wf_id_alloc(rid,decision,who,"",slen("WFDEC rid= step= decision= by=\n")+slen("9223372036854775807")+1) 671 if ln==(0 as *u8){return WF_EVIDENCE_ERROR} 672 var o: i64 = 0 673 o = wf_cat(ln, o, "WFDEC rid=" as *u8) 674 o = wf_cat(ln, o, rid) 675 o = wf_cat(ln, o, " step=" as *u8) 676 o = wf_catn(ln, o, idx) 677 o = wf_cat(ln, o, " decision=" as *u8) 678 o = wf_cat(ln, o, decision) 679 o = wf_cat(ln, o, " by=" as *u8) 680 o = wf_cat(ln, o, who) 681 ln[o] = 10 as u8 682 ln[o+1] = 0 as u8 683 let rc:i64=wf_append(led,ln) 684 sys_munmap_direct(ln,slen(ln)+1) 685 return rc 686} 687 688// ---- R8 data passing: run-scoped variables on the SAME event-sourced ledger ---- 689// WFVAR rid=<rid> k=<key> v=<value-to-end-of-line>. Trigger-event fields auto-bind at fire; a set-var 690// step writes derived vars; {key} placeholders in any step arg substitute at execution; LAST write wins; 691// unknown placeholder = LOUD step failure (the send_merge law). Values must not contain ~ or newline. 692func wf_emit_var(led: *u8, rid: *u8, k: *u8, v: *u8) -> i64 { 693 // Allocate the complete serialized record, including newline and terminator. 694 var bytes: i64 = slen("WFVAR rid=" as *u8)+slen(" k=" as *u8)+slen(" v=" as *u8)+2 695 let rn: i64 = slen(rid);let kn: i64 = slen(k);let vn: i64 = slen(v) 696 if rn>9223372036854775807-bytes{return 0-1};bytes=bytes+rn 697 if kn>9223372036854775807-bytes{return 0-1};bytes=bytes+kn 698 if vn>9223372036854775807-bytes{return 0-1};bytes=bytes+vn 699 let ln: *u8 = sys_mmap_try(bytes) 700 if (ln as i64)<=0{p("WFLOW variable ERROR allocation refused; record not appended\n" as *u8);return 0-1} 701 var o: i64 = 0 702 o = wf_cat(ln, o, "WFVAR rid=" as *u8) 703 o = wf_cat(ln, o, rid) 704 o = wf_cat(ln, o, " k=" as *u8) 705 o = wf_cat(ln, o, k) 706 o = wf_cat(ln, o, " v=" as *u8) 707 o = wf_cat(ln, o, v) 708 ln[o] = 10 as u8 709 ln[o+1] = 0 as u8 710 let rc:i64=wf_append(led,ln) 711 let released:i64=sys_munmap_direct(ln,bytes) 712 if released!=0{return 0-1} 713 return rc 714} 715// copy value after key to END OF LINE (values may contain spaces) 716func wf_val_line_end(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8, cap: i64) -> i64 { 717 let m: i64 = slen(key) 718 var i: i64 = ls 719 while i + m <= le { 720 var k: i64 = 0 721 var hit: i64 = 1 722 while k < m { if buf[i+k] != key[k] { hit = 0; k = m } else { k = k + 1 } } 723 if hit == 1 { 724 var q: i64 = i + m 725 var t: i64 = 0 726 while q < le { if t < cap - 1 { out[t] = buf[q]; t = t + 1 } q = q + 1 } 727 out[t] = 0 as u8 728 return 1 729 } 730 i = i + 1 731 } 732 out[0] = 0 as u8 733 return 0 734} 735func wf_obs_tok_fwd(tok: *u8, rid: *u8) -> i64 { 736 var q: i64 = 0 737 q = wf_cat(tok, q, " rid=" as *u8) 738 q = wf_cat(tok, q, rid) 739 tok[q] = 32 as u8 740 tok[q+1] = 0 as u8 741 return q 742} 743// Latest WFVAR value: 1 found, 0 missing, -1 unresolved or insufficient capacity; no partial output. 744func wf_var_get(cx: *i64, rid: *u8, key: *u8, out: *u8, cap: i64) -> i64 { 745 if cap<=0{return 0-1};out[0]=0 746 let owned:*WfOwnedText=wf_var_owned(cx,rid,key) 747 if owned==(0 as *WfOwnedText){return 0-1} 748 var rc:i64=owned.status 749 if owned.status==1{ 750 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} 751 } 752 if wf_text_free(owned)!=0{out[0]=0;return 0-1} 753 return rc 754} 755// Substitute {key} from one owned ledger snapshot; -1 on unresolved input, allocation or insufficient output. 756func wf_subst(cx: *i64, rid: *u8, src: *u8, out: *u8, cap: i64) -> i64 { 757 if cap<=0{return 0-1};out[0]=0 758 let owned:*WfOwnedText=wf_subst_owned(cx,rid,src) 759 if owned==(0 as *WfOwnedText){return 0-1} 760 var rc:i64=owned.len 761 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} 762 if wf_text_free(owned)!=0{out[0]=0;return 0-1} 763 return rc 764} 765// bind every k=v field of the trigger event as an initial run variable (segment 0 = kind, skipped) 766func wf_bind_event(cx: *i64, rid: *u8, ev: *u8) -> i64 { 767 let n:i64=slen(ev) 768 if n==9223372036854775807{return WF_EVIDENCE_ERROR} 769 let bytes:i64=n+1 770 let kb:*u8=sys_mmap_try(bytes) 771 if (kb as i64)<=0{return WF_EVIDENCE_ERROR} 772 let vb:*u8=sys_mmap_try(bytes) 773 if (vb as i64)<=0{sys_munmap_direct(kb,bytes);return WF_EVIDENCE_ERROR} 774 var i:i64=0 775 var bound:i64=0 776 var result:i64=0 777 while i<n { 778 if ev[i]!=(126 as u8){i=i+1} 779 else { 780 var j:i64=i+1 781 var kn:i64=0 782 var haseq:i64=0 783 while j<n { 784 if ev[j]==(126 as u8){break} 785 if ev[j]==(61 as u8){haseq=1;j=j+1;break} 786 kb[kn]=ev[j];kn=kn+1;j=j+1 787 } 788 kb[kn]=0 as u8 789 if haseq==1 { 790 var vn:i64=0 791 while j<n { 792 if ev[j]==(126 as u8){break} 793 vb[vn]=ev[j];vn=vn+1;j=j+1 794 } 795 vb[vn]=0 as u8 796 if kn>0 { 797 if wf_emit_var(cx[2] as *u8,rid,kb,vb)!=0{result=WF_EVIDENCE_ERROR;break} 798 bound=bound+1 799 } 800 } 801 i=j 802 } 803 } 804 let kr:i64=sys_munmap_direct(kb,bytes) 805 let vr:i64=sys_munmap_direct(vb,bytes) 806 if result!=0||kr!=0||vr!=0{return WF_EVIDENCE_ERROR} 807 return bound 808} 809 810// ---- R12 versioning: @version N directive in the flows file; runs stamp WFVER; resume flags drift ---- 811func wf_defs_version(path: *u8) -> i64 { 812 let szp: *i64 = sys_mmap(8) as *i64 813 let buf: *u8 = wf_read_snapshot(path, szp) 814 if (buf as i64) == 0 { return 0 } 815 let n: i64 = szp[0] 816 var ls: i64 = 0 817 while ls < n { 818 var le: i64 = ls 819 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 } 820 if wf_has_rng(buf, ls, le, "@version " as *u8) == 1 { 821 let v: i64 = wf_num_after(buf, ls, le, "@version " as *u8) 822 if v > 0 { return v } 823 } 824 ls = le + 1 825 } 826 return 0 827} 828func wf_emit_ver(led: *u8, rid: *u8, ver: i64) -> i64 { 829 let ln: *u8 = wf_id_alloc(rid,"","","",slen("WFVER rid= ver=\n")+slen("9223372036854775807")+1) 830 if ln==(0 as *u8){return WF_EVIDENCE_ERROR} 831 var o: i64 = 0 832 o = wf_cat(ln, o, "WFVER rid=" as *u8) 833 o = wf_cat(ln, o, rid) 834 o = wf_cat(ln, o, " ver=" as *u8) 835 o = wf_catn(ln, o, ver) 836 ln[o] = 10 as u8 837 ln[o+1] = 0 as u8 838 let rc:i64=wf_append(led,ln) 839 sys_munmap_direct(ln,slen(ln)+1) 840 return rc 841} 842func wf_emit_drift(led: *u8, rid: *u8, ranv: i64, nowv: i64) -> i64 { 843 let ln: *u8 = wf_id_alloc(rid,"","","",slen("WFVERDRIFT rid= ran= now=\n")+slen("9223372036854775807")*2+1) 844 if ln==(0 as *u8){return WF_EVIDENCE_ERROR} 845 var o: i64 = 0 846 o = wf_cat(ln, o, "WFVERDRIFT rid=" as *u8) 847 o = wf_cat(ln, o, rid) 848 o = wf_cat(ln, o, " ran=" as *u8) 849 o = wf_catn(ln, o, ranv) 850 o = wf_cat(ln, o, " now=" as *u8) 851 o = wf_catn(ln, o, nowv) 852 ln[o] = 10 as u8 853 ln[o+1] = 0 as u8 854 let rc:i64=wf_append(led,ln) 855 sys_munmap_direct(ln,slen(ln)+1) 856 return rc 857} 858// the version a run was STARTED under (last WFVER line for the rid; 0 = unversioned) 859func wf_run_ver(buf: *u8, n: i64, ridtok: *u8) -> i64 { 860 var v: i64 = 0 861 var ls: i64 = 0 862 while ls < n { 863 var le: i64 = ls 864 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 } 865 if wf_has_rng(buf, ls, le, "WFVER " as *u8) == 1 { if wf_has_rng(buf, ls, le, ridtok) == 1 { 866 let x: i64 = wf_num_after(buf, ls, le, " ver=" as *u8) 867 if x > 0 { v = x } 868 } } 869 ls = le + 1 870 } 871 return v 872} 873 874// ---- R13 templates: gallery = <dir>/index (name|description rows) + <name>.flows/.steps pairs ---- 875func wf_tpl_exists(path: *u8) -> i64 { 876 let fd: i64 = __syscall(257, 0 - 100, path as i64, 0, 0, 0, 0) 877 if fd < 0 { return 0 } 878 __syscall(3, fd, 0, 0, 0, 0, 0) 879 return 1 880} 881func wf_tpl_path(dir: *u8, name: *u8, ext: *u8, out: *u8) -> i64 { 882 var o: i64 = 0 883 o = wf_cat(out, o, dir) 884 out[o] = 47 as u8 885 o = o + 1 886 o = wf_cat(out, o, name) 887 o = wf_cat(out, o, ext) 888 out[o] = 0 as u8 889 return o 890} 891func wf_tpl_index_path(dir: *u8, out: *u8) -> i64 { 892 var o: i64 = 0 893 o = wf_cat(out, o, dir) 894 out[o] = 47 as u8 895 o = o + 1 896 o = wf_cat(out, o, "index" as *u8) 897 out[o] = 0 as u8 898 return o 899} 900// list the gallery: every index row printed with file-existence + version; returns USABLE count, -1 loud 901func wf_tpl_list(dir: *u8) -> i64 { 902 let ip: *u8 = sys_mmap(512) 903 wf_tpl_index_path(dir, ip) 904 let szp: *i64 = sys_mmap(8) as *i64 905 let buf: *u8 = wf_read_snapshot(ip, szp) 906 if (buf as i64) == 0 { return 0 - 1 } 907 let n: i64 = szp[0] 908 if n == 0 { p("WFLOW templates ERROR missing gallery index -- fail loud\n" as *u8); return 0 - 1 } 909 let nm: *u8 = sys_mmap(128) 910 let ds: *u8 = sys_mmap(512) 911 let fp2: *u8 = sys_mmap(512) 912 let sp2: *u8 = sys_mmap(512) 913 var cnt: i64 = 0 914 var ls: i64 = 0 915 while ls < n { 916 var le: i64 = ls 917 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 } 918 var keep: i64 = 0 919 if le > ls { keep = 1 } 920 if keep == 1 { if buf[ls] == (35 as u8) { keep = 0 } } 921 if keep == 1 { 922 let row: *u8 = sys_mmap(le - ls + 2) 923 var k: i64 = 0 924 while ls + k < le { row[k] = buf[ls+k]; k = k + 1 } 925 row[k] = 0 as u8 926 pipe_field(row, 0, nm, 128) 927 pipe_field(row, 1, ds, 512) 928 wf_tpl_path(dir, nm, ".flows" as *u8, fp2) 929 wf_tpl_path(dir, nm, ".steps" as *u8, sp2) 930 let fe: i64 = wf_tpl_exists(fp2) 931 let se: i64 = wf_tpl_exists(sp2) 932 let v: i64 = wf_defs_version(fp2) 933 p("WFTPL name=" as *u8); p(nm) 934 p(" ver=" as *u8); pn(v) 935 p(" flows=" as *u8); pn(fe) 936 p(" steps=" as *u8); pn(se) 937 p(" desc=" as *u8); p(ds) 938 p("\n" as *u8) 939 if fe == 1 { if se == 1 { cnt = cnt + 1 } } 940 } 941 ls = le + 1 942 } 943 p("WFTPL-TOTAL usable=" as *u8); pn(cnt); p("\n" as *u8) 944 return cnt 945} 946func wf_tpl_indexed(dir: *u8, name: *u8) -> i64 { 947 let ip: *u8 = sys_mmap(512) 948 wf_tpl_index_path(dir, ip) 949 let szp: *i64 = sys_mmap(8) as *i64 950 let buf: *u8 = wf_read_snapshot(ip, szp) 951 if (buf as i64) == 0 { return 0 - 1 } 952 let n: i64 = szp[0] 953 if n == 0 { return 0 - 1 } 954 let nm: *u8 = sys_mmap(128) 955 var ls: i64 = 0 956 while ls < n { 957 var le: i64 = ls 958 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 } 959 if le > ls { if buf[ls] != (35 as u8) { 960 let row: *u8 = sys_mmap(le - ls + 2) 961 var k: i64 = 0 962 while ls + k < le { row[k] = buf[ls+k]; k = k + 1 } 963 row[k] = 0 as u8 964 pipe_field(row, 0, nm, 128) 965 if seq(nm, name) == 1 { return 1 } 966 } } 967 ls = le + 1 968 } 969 return 0 970} 971// instantiate: fail-closed to INDEXED templates; byte-copy both files; whole-set validate the COPY 972// (a broken template never lands silently). Returns the template version (0 = unversioned), -1 loud. 973func wf_tpl_instantiate(dir: *u8, name: *u8, df: *u8, ds: *u8) -> i64 { 974 let ix: i64 = wf_tpl_indexed(dir, name) 975 if ix != 1 { p("WFLOW instantiate ERROR template not in gallery index -- fail closed\n" as *u8); return 0 - 1 } 976 let fp2: *u8 = sys_mmap(512) 977 let sp2: *u8 = sys_mmap(512) 978 wf_tpl_path(dir, name, ".flows" as *u8, fp2) 979 wf_tpl_path(dir, name, ".steps" as *u8, sp2) 980 let szp: *i64 = sys_mmap(8) as *i64 981 let fb: *u8 = wf_read_snapshot(fp2, szp) 982 if (fb as i64) == 0 { return 0 - 1 } 983 if szp[0] == 0 { p("WFLOW instantiate ERROR template flows file missing -- fail loud\n" as *u8); return 0 - 1 } 984 if wf_writeall(df, fb, szp[0]) != 0 { return 0 - 1 } 985 let sb: *u8 = wf_read_snapshot(sp2, szp) 986 if (sb as i64) == 0 { return 0 - 1 } 987 if szp[0] == 0 { p("WFLOW instantiate ERROR template steps file missing -- fail loud\n" as *u8); return 0 - 1 } 988 if wf_writeall(ds, sb, szp[0]) != 0 { return 0 - 1 } 989 let fl: *i64 = sys_mmap(8 * 64) as *i64 990 let st2: *i64 = sys_mmap(8 * 128) as *i64 991 let nf: i64 = wf_lines_load(df, fl, 64) 992 if nf < 1 { return 0 - 1 } 993 let nst: i64 = wf_lines_load(ds, st2, 128) 994 if nst < 1 { return 0 - 1 } 995 if wf_load_flows(fl, nf) < 0 { return 0 - 1 } 996 if wf_load(st2, nst) < 0 { return 0 - 1 } 997 let v: i64 = wf_defs_version(df) 998 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) 999 return v 1000} 1001 1002// R9 branching: per-step condition k=v against the run variable plane. Missing variable or 1003// mismatch = 0 (the step SKIPs, recorded in the ledger); match = 1. Equality only, v1. 1004func wf_cond_ok(cx: *i64, rid: *u8, cond: *u8) -> i64 { 1005 let n:i64=slen(cond);var split:i64=0 1006 while split<n{if cond[split]==(61 as u8){break};split=split+1} 1007 if split==0{return 0};if split==n{return 0} 1008 let key:*WfOwnedText=wf_text_slice(cond,0,split) 1009 if key==(0 as *WfOwnedText){return 0-1} 1010 let value:*WfOwnedText=wf_var_owned(cx,rid,key.data) 1011 wf_text_free(key) 1012 if value==(0 as *WfOwnedText){return 0-1} 1013 var result:i64=0 1014 if value.status==1{result=seq(value.data,((cond as i64)+split+1) as *u8)} 1015 if value.status<0{result=0-1} 1016 if wf_text_free(value)!=0{return 0-1};return result 1017} 1018 1019// ---- the durable executor: run flow fid for run rid, starting AFTER step s0 ---- 1020// cx bundle: cx[0]=steps ptr, cx[1]=nsteps, cx[2]=ledger path 1021func wf_run_from(cx: *i64, rid: *u8, fid: *u8, s0: i64) -> i64 { 1022 let steps:*i64=cx[0] as *i64;var i:i64=0;var longest:i64=0 1023 while i<cx[1]{let n:i64=slen(steps[i] as *u8);if n>longest{longest=n};i=i+1} 1024 if longest==WF_TEXT_I64_MAX{return WF_EVIDENCE_ERROR} 1025 let scratch:*WfRunScratch=wf_run_scratch_new(longest+1) 1026 if scratch==(0 as *WfRunScratch){return WF_EVIDENCE_ERROR} 1027 let rc:i64=wf_run_from_owned(cx,rid,fid,s0,scratch) 1028 if wf_run_scratch_free(scratch)!=0{return WF_EVIDENCE_ERROR} 1029 return rc 1030} 1031 1032func wf_run_from_owned(cx: *i64, rid: *u8, fid: *u8, s0: i64, scratch:*WfRunScratch) -> i64 { 1033 let steps: *i64 = cx[0] as *i64 1034 let ns: i64 = cx[1] 1035 let led: *u8 = cx[2] as *u8 1036 let f2: *u8 = scratch.fields.fid 1037 let ix: *u8 = scratch.fields.index 1038 let a: *u8 = scratch.fields.action 1039 let ch: *u8 = scratch.fields.channel 1040 let arg: *u8 = scratch.fields.arg 1041 let ma: *u8 = scratch.fields.retry 1042 var i: i64 = 0 1043 var done: i64 = 0 1044 var failed: i64 = 0 1045 var parked: i64 = 0 1046 var jumpto: i64 = 0 1047 while i < ns { 1048 if wf_text_free(scratch.prepared)!=0{return WF_EVIDENCE_ERROR};scratch.prepared=0 as *WfOwnedText 1049 let r: *u8 = steps[i] as *u8 1050 pipe_field(r, 0, f2, scratch.cap) 1051 var use: i64 = 0 1052 if seq(f2, fid) == 1 { use = 1 } 1053 var idx: i64 = 0 1054 if use == 1 { pipe_field(r, 1, ix, scratch.cap); idx = wf_atoi(ix); if idx <= s0 { use = 0 } } 1055 // R10: an active on-fail jump skips the remaining normal-path steps (recorded) until the target 1056 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 } } } 1057 if use == 1 { 1058 pipe_field(r, 2, a, scratch.cap) 1059 pipe_field(r, 3, ch, scratch.cap) 1060 pipe_field(r, 4, arg, scratch.cap) 1061 pipe_field(r, 5, ma, scratch.cap) 1062 // R9: optional per-step condition (7th field, k=v) vs the variable plane -> SKIP on mismatch 1063 let cond: *u8 = scratch.fields.condition 1064 pipe_field(r, 6, cond, scratch.cap) 1065 var argx: *u8 = arg 1066 var mode: i64 = 0 1067 var docond: i64 = 0 1068 if cond[0] != (0 as u8) { docond = 1 } 1069 if cond[0] == (45 as u8) { if cond[1] == (0 as u8) { docond = 0 } } 1070 if docond == 1 { let condition:i64=wf_cond_ok(cx,rid,cond);if condition==0{mode=4};if condition<0{mode=3} } 1071 // R8: resolve {key} placeholders from the run variable plane; unknown = LOUD step failure. 1072 // for-each bypasses step-level subst ({item} binds inside the loop) -- the loop substitutes 1073 // the list once and the per-item template each iteration. 1074 if mode == 0 { 1075 var rawarg: i64 = 0 1076 if seq(a, "for-each" as *u8) == 1 { rawarg = 1 } 1077 if rawarg==0 { 1078 scratch.prepared=wf_subst_owned(cx,rid,arg) 1079 if scratch.prepared==(0 as *WfOwnedText){mode=3}else{argx=scratch.prepared.data} 1080 } 1081 } 1082 if mode == 4 { 1083 p("WFLOW-SKIP run=" as *u8); p(rid); p(" step=" as *u8); pn(idx); p(" cond=" as *u8); p(cond); p("\n" as *u8) 1084 if wf_emit(led, rid, fid, idx, "SKIP" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1085 } 1086 if mode == 0 { if seq(a, "set-var" as *u8) == 1 { mode = 2 } } 1087 if mode == 0 { if seq(a, "approve" as *u8) == 1 { mode = 1 } } 1088 if mode == 3 { 1089 if wf_emit(led, rid, fid, idx, "FAILSTEP" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1090 if wf_emit(led, rid, fid, idx, "FAILED" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1091 failed = 1 1092 i = ns 1093 } 1094 if mode == 2 { 1095 var e:i64=0 1096 while argx[e]!=(0 as u8){if argx[e]==(61 as u8){break};e=e+1} 1097 if argx[e]==(61 as u8){if e>0{ 1098 argx[e]=0 1099 let kb:*u8=argx 1100 let vb:*u8=((argx as i64)+e+1) as *u8 1101 if wf_emit_var(led, rid, kb, vb) != 0 { return WF_EVIDENCE_ERROR } 1102 p("WFLOW-SETVAR run=" as *u8); p(rid); p(" k=" as *u8); p(kb); p(" v=" as *u8); p(vb); p("\n" as *u8) 1103 if wf_emit(led, rid, fid, idx, "OK" as *u8, 1) != 0 { return WF_EVIDENCE_ERROR } 1104 done = done + 1 1105 } else { mode = 3 } } else { mode = 3 } 1106 if mode == 3 { 1107 p("WFLOW set-var ERROR needs key=value -- fail loud\n" as *u8) 1108 if wf_emit(led, rid, fid, idx, "FAILSTEP" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1109 if wf_emit(led, rid, fid, idx, "FAILED" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1110 failed = 1 1111 i = ns 1112 } 1113 } 1114 if mode == 1 { 1115 let dec: i64 = wf_decision(cx, rid, idx) 1116 if dec == 1 { 1117 p("WFLOW-APPROVAL granted run=" as *u8); p(rid); p(" step=" as *u8); pn(idx); p("\n" as *u8) 1118 if wf_emit(led, rid, fid, idx, "OK" as *u8, 1) != 0 { return WF_EVIDENCE_ERROR } 1119 done = done + 1 1120 } 1121 if dec == (0 - 1) { 1122 p("WFLOW-APPROVAL DENIED run=" as *u8); p(rid); p(" step=" as *u8); pn(idx); p(" -- run fails (deny wins)\n" as *u8) 1123 if wf_emit(led, rid, fid, idx, "FAILSTEP" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1124 if wf_emit(led, rid, fid, idx, "FAILED" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1125 failed = 1 1126 i = ns 1127 } 1128 if dec == 0 { 1129 p("WFLOW-APPROVAL pending run=" as *u8); p(rid); p(" step=" as *u8); pn(idx) 1130 p(" approver=" as *u8); p(ch); p(" what=" as *u8); p(argx); p(" -- run PARKED\n" as *u8) 1131 if wf_emit(led, rid, fid, idx, "WAIT" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1132 parked = 1 1133 i = ns 1134 } 1135 } 1136 if mode == 0 { 1137 let maxa: i64 = wf_atoi(ma) 1138 var att: i64 = 1 1139 var okd: i64 = 0 1140 while att <= maxa { 1141 if wf_emit(led, rid, fid, idx, "ATT" as *u8, att) != 0 { return WF_EVIDENCE_ERROR } 1142 let rc: i64 = wf_exec(cx, a, ch, argx, att, rid) 1143 if rc == WF_EVIDENCE_ERROR { return rc } 1144 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 } 1145 } 1146 if okd == 0 { 1147 if wf_emit(led, rid, fid, idx, "FAILSTEP" as *u8, maxa) != 0 { return WF_EVIDENCE_ERROR } 1148 // R10: on-fail routing (8th field) -- exhausted ACTION failures only; the failure stays 1149 // recorded (FAILSTEP + ONFAIL), wf_error vars land on the plane, execution jumps forward 1150 let onf: *u8 = scratch.fields.onfail 1151 pipe_field(r, 7, onf, scratch.cap) 1152 var tj: i64 = 0 1153 var haveof: i64 = 0 1154 if onf[0] != (0 as u8) { haveof = 1 } 1155 if onf[0] == (45 as u8) { if onf[1] == (0 as u8) { haveof = 0 } } 1156 if haveof == 1 { tj = wf_atoi(onf) } 1157 if tj > 0 { 1158 if wf_emit(led, rid, fid, idx, "ONFAIL" as *u8, tj) != 0 { return WF_EVIDENCE_ERROR } 1159 if wf_emit_var(led, rid, "wf_error" as *u8, "yes" as *u8) != 0 { return WF_EVIDENCE_ERROR } 1160 let sv: *u8 = sys_mmap(32) 1161 var so: i64 = 0 1162 so = wf_cat(sv, so, "step" as *u8) 1163 so = wf_catn(sv, so, idx) 1164 sv[so] = 0 as u8 1165 if wf_emit_var(led, rid, "wf_error_step" as *u8, sv) != 0 { return WF_EVIDENCE_ERROR } 1166 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) 1167 jumpto = tj 1168 } else { 1169 if wf_emit(led, rid, fid, idx, "FAILED" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1170 failed = 1 1171 i = ns 1172 } 1173 } else { done = done + 1 } 1174 } 1175 } 1176 i = i + 1 1177 } 1178 if failed == 1 { return 0 - 2 } 1179 if parked == 1 { return 0 - 3 } 1180 if wf_emit(led, rid, fid, 0, "DONE" as *u8, 0) != 0 { return WF_EVIDENCE_ERROR } 1181 return done 1182} 1183 1184// ---- fire an event: durable ledger-deduped, one run per matched flow ---- 1185func wf_fire(cx:*i64,flows:*i64,nf:i64,ev:*u8)->i64{ 1186 let event:*WfOwnedText=wf_id_event(ev);if event==(0 as *WfOwnedText){p("WFLOW identity ERROR invalid event; no dispatch\n");return 0-1} 1187 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) 1188 if buf==(0 as *u8){wf_text_free(event);sys_munmap_direct(sz as *u8,__size_of(i64));return WF_EVIDENCE_ERROR} 1189 var started:i64=0;var matched:i64=0;var f:i64=0;var rc:i64=0 1190 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} 1191 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 1192} 1193 1194func wf_flow_known(cx:*i64,fid:*u8)->i64{ 1195 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 1196} 1197 1198func wf_resume(cx:*i64)->i64{ 1199 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){return WF_EVIDENCE_ERROR} 1200 let n:i64=sz[0];var resumed:i64=0;var ls:i64=0;var rc:i64=0 1201 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} 1202 if wf_text_match(buf,ls,le,"WFRUN rid=",10)==1{ 1203 let status:*WfOwnedText=wf_id_field(buf,ls,le," status=");if status==(0 as *WfOwnedText){rc=WF_EVIDENCE_ERROR;break} 1204 if seq(status.data,"START")==1{ 1205 let rid:*WfOwnedText=wf_id_field(buf,ls,le," rid=");let fid:*WfOwnedText=wf_id_field(buf,ls,le," flow=") 1206 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} 1207 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} 1208 if wf_lines_with2(buf,n,token.data,"status=DONE")==0&&wf_lines_with2(buf,n,token.data,"status=FAILED")==0{ 1209 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} 1210 if rc==0{if wf_flow_known(cx,fid.data)!=1{p("WFLOW resume ERROR unknown flow; no dispatch\n");rc=0-1}} 1211 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}}} 1212 if rc==0{let first:i64=wf_max_ok_step(buf,n,token.data);let run:i64=wf_run_from(cx,rid.data,fid.data,first);if run==WF_EVIDENCE_ERROR{rc=run}else{resumed=resumed+1}} 1213 };wf_text_free(token);wf_text_free(rid);wf_text_free(fid) 1214 };wf_text_free(status) 1215 };if rc!=0{break};ls=le+1 1216 };sys_munmap_direct(buf,sz[0]+1);sys_munmap_direct(sz as *u8,__size_of(i64));if rc!=0{return rc};return resumed 1217} 1218 1219func wf_lines_load(path: *u8, arr: *i64, cap: i64) -> i64 { 1220 let szp: *i64 = sys_mmap(8) as *i64 1221 let buf: *u8 = wf_read_snapshot(path, szp) 1222 if (buf as i64) == 0 { return 0 - 1 } 1223 let n: i64 = szp[0] 1224 if n == 0 { p("WFLOW files ERROR missing or empty definition file -- fail loud\n" as *u8); return 0 - 1 } 1225 var cnt: i64 = 0 1226 var ls: i64 = 0 1227 while ls < n { 1228 var le: i64 = ls 1229 while le < n { if buf[le] == (10 as u8) { break } le = le + 1 } 1230 var keep: i64 = 0 1231 if le > ls { keep = 1 } 1232 if keep == 1 { if buf[ls] == (35 as u8) { keep = 0 } } 1233 if keep == 1 { if buf[ls] == (64 as u8) { keep = 0 } } 1234 if keep == 1 { 1235 if cnt >= cap { p("WFLOW files ERROR too many definition rows -- fail loud\n" as *u8); return 0 - 1 } 1236 let s: *u8 = sys_mmap(le - ls + 2) 1237 var k: i64 = 0 1238 while ls + k < le { s[k] = buf[ls+k]; k = k + 1 } 1239 s[k] = 0 as u8 1240 arr[cnt] = s as i64 1241 cnt = cnt + 1 1242 } 1243 ls = le + 1 1244 } 1245 return cnt 1246} 1247func wf_cx_files(flowsp: *u8, stepsp: *u8, led: *u8, cat: *u8, cx: *i64, flows: *i64) -> i64 { 1248 let steps: *i64 = sys_mmap(8 * 128) as *i64 1249 let nf: i64 = wf_lines_load(flowsp, flows, 64) 1250 if nf < 1 { return 0 - 1 } 1251 let nst: i64 = wf_lines_load(stepsp, steps, 128) 1252 if nst < 1 { return 0 - 1 } 1253 if wf_load_flows(flows, nf) < 0 { return 0 - 1 } 1254 if wf_load(steps, nst) < 0 { return 0 - 1 } 1255 cx[0] = steps as i64 1256 cx[1] = nst 1257 cx[2] = led as i64 1258 cx[3] = 0 1259 var usecat: i64 = 1 1260 if cat[0] == (45 as u8) { if cat[1] == (0 as u8) { usecat = 0 } } 1261 if usecat == 1 { cx[3] = cat as i64 } 1262 cx[4] = wf_defs_version(flowsp) 1263 return nf 1264} 1265func wf_fire_files(flowsp: *u8, stepsp: *u8, led: *u8, cat: *u8, ev: *u8) -> i64 { 1266 let cx: *i64 = sys_mmap(40) as *i64 1267 let flows: *i64 = sys_mmap(8 * 64) as *i64 1268 let nf: i64 = wf_cx_files(flowsp, stepsp, led, cat, cx, flows) 1269 if nf < 0 { return 0 - 1 } 1270 return wf_fire(cx, flows, nf, ev) 1271} 1272func wf_resume_files(flowsp: *u8, stepsp: *u8, led: *u8, cat: *u8) -> i64 { 1273 let cx: *i64 = sys_mmap(40) as *i64 1274 let flows: *i64 = sys_mmap(8 * 64) as *i64 1275 let nf: i64 = wf_cx_files(flowsp, stepsp, led, cat, cx, flows) 1276 if nf < 0 { return 0 - 1 } 1277 return wf_resume(cx) 1278} 1279 1280// ---- R6 run observability: board + 0-JS HTML derived ENTIRELY by ledger replay (nothing self-reported) ---- 1281func wf_state_name(st: i64) -> *u8 { 1282 if st == 3 { return "DONE" as *u8 } 1283 if st == 2 { return "FAILED" as *u8 } 1284 if st == 1 { return "PARKED" as *u8 } 1285 return "RUNNING" as *u8 1286} 1287// derived state: DONE > FAILED > PARKED(waiting approval) > RUNNING(in-flight) 1288func wf_run_state(buf: *u8, n: i64, tok: *u8) -> i64 { 1289 if wf_lines_with2(buf, n, tok, "status=DONE" as *u8) > 0 { return 3 } 1290 if wf_lines_with2(buf, n, tok, "status=FAILED" as *u8) > 0 { return 2 } 1291 if wf_lines_with2(buf, n, tok, "status=WAIT" as *u8) > 0 { return 1 } 1292 return 0 1293} 1294// collect distinct run ids from START lines; returns count, -1 loud over cap 1295func wf_obs_runs(buf:*u8,n:i64,rids:*i64,cap:i64)->i64{ 1296 var count:i64=0;var ls:i64=0 1297 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} 1298 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} 1299 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} 1300 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)} 1301 };wf_text_free(st) 1302 };ls=le+1 1303 };return count 1304} 1305 1306func wf_obs_tok(tok: *u8, rid: *u8) -> i64 { 1307 var q: i64 = 0 1308 q = wf_cat(tok, q, " rid=" as *u8) 1309 q = wf_cat(tok, q, rid) 1310 tok[q] = 32 as u8 1311 tok[q+1] = 0 as u8 1312 return q 1313} 1314func wf_obs_board(led: *u8) -> i64 { 1315 let szp: *i64 = sys_mmap(8) as *i64 1316 let buf: *u8 = wf_read_snapshot(led, szp) 1317 if (buf as i64) == 0 { return 0 - 1 } 1318 let n: i64 = szp[0] 1319 let slots:i64=n/slen("WFRUN rid=")+1 1320 let rids:*i64=sys_mmap_try(slots*__size_of(i64)) as *i64 1321 if (rids as i64)<=0{return 0-1} 1322 let cnt:i64=wf_obs_runs(buf,n,rids,slots) 1323 if cnt < 0 { return 0 - 1 } 1324 let tok:*u8=sys_mmap_try(n+1) 1325 if (tok as i64)<=0{return 0-1} 1326 var d: i64 = 0 1327 var f: i64 = 0 1328 var w: i64 = 0 1329 var ru: i64 = 0 1330 var i: i64 = 0 1331 while i < cnt { 1332 let rid: *u8 = rids[i] as *u8 1333 wf_obs_tok(tok, rid) 1334 let st: i64 = wf_run_state(buf, n, tok) 1335 let oks: i64 = wf_lines_with2(buf, n, tok, "status=OK" as *u8) 1336 let ats: i64 = wf_lines_with2(buf, n, tok, "status=ATT" as *u8) 1337 p("WFOBS run=" as *u8); p(rid) 1338 p(" state=" as *u8); p(wf_state_name(st)) 1339 p(" oksteps=" as *u8); pn(oks) 1340 p(" attempts=" as *u8); pn(ats) 1341 p("\n" as *u8) 1342 if st == 3 { d = d + 1 } 1343 if st == 2 { f = f + 1 } 1344 if st == 1 { w = w + 1 } 1345 if st == 0 { ru = ru + 1 } 1346 i = i + 1 1347 } 1348 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) 1349 return cnt 1350} 1351// 0-JS sovereign run board page; unquoted HTML5 attrs + rgb() colors (no quote/hash/bang literals) 1352func wf_obs_html(led: *u8, out: *u8) -> i64 { 1353 let szp: *i64 = sys_mmap(8) as *i64 1354 let buf: *u8 = wf_read_snapshot(led, szp) 1355 if (buf as i64) == 0 { return 0 - 1 } 1356 let n: i64 = szp[0] 1357 let slots:i64=n/slen("WFRUN rid=")+1 1358 let rids:*i64=sys_mmap_try(slots*__size_of(i64)) as *i64 1359 if (rids as i64)<=0{return 0-1} 1360 let cnt:i64=wf_obs_runs(buf,n,rids,slots) 1361 if cnt < 0 { return 0 - 1 } 1362 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(" &middot; done ")+slen(" &middot; failed ")+slen(" &middot; parked ")+slen(" &middot; running ")+slen("</p><p>Generated by nx_wflow_engine wf_obs_html (sovereign, 0-JS, ledger replay).</p></body></html>")+2+slen("9223372036854775807")*5 1363 var measure:i64=0 1364 while measure<cnt{let escaped:*WfOwnedText=wf_id_html(rids[measure] as *u8);if escaped==(0 as *WfOwnedText){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);return 0-1};needed=needed+extra;wf_text_free(escaped);measure=measure+1} 1365 let hb:*u8=sys_mmap_try(needed) 1366 if (hb as i64)<=0{return 0-1} 1367 var o: i64 = 0 1368 o = wf_cat(hb, o, "<" as *u8) 1369 hb[o] = 33 as u8 1370 o = o + 1 1371 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) 1372 let tok:*u8=sys_mmap_try(n+1) 1373 if (tok as i64)<=0{return 0-1} 1374 var d: i64 = 0 1375 var f: i64 = 0 1376 var w: i64 = 0 1377 var ru: i64 = 0 1378 var i: i64 = 0 1379 while i < cnt { 1380 let rid: *u8 = rids[i] as *u8 1381 wf_obs_tok(tok, rid) 1382 let st: i64 = wf_run_state(buf, n, tok) 1383 let oks: i64 = wf_lines_with2(buf, n, tok, "status=OK" as *u8) 1384 let ats: i64 = wf_lines_with2(buf, n, tok, "status=ATT" as *u8) 1385 o = wf_cat(hb, o, "<tr><td>" as *u8) 1386 let escaped:*WfOwnedText=wf_id_html(rid) 1387 if escaped==(0 as *WfOwnedText){return 0-1} 1388 o=wf_cat(hb,o,escaped.data);wf_text_free(escaped) 1389 o = wf_cat(hb, o, "</td><td class=" as *u8) 1390 o = wf_cat(hb, o, wf_state_name(st)) 1391 o = wf_cat(hb, o, ">" as *u8) 1392 o = wf_cat(hb, o, wf_state_name(st)) 1393 o = wf_cat(hb, o, "</td><td>" as *u8) 1394 o = wf_catn(hb, o, oks) 1395 o = wf_cat(hb, o, "</td><td>" as *u8) 1396 o = wf_catn(hb, o, ats) 1397 o = wf_cat(hb, o, "</td></tr>" as *u8) 1398 if st == 3 { d = d + 1 } 1399 if st == 2 { f = f + 1 } 1400 if st == 1 { w = w + 1 } 1401 if st == 0 { ru = ru + 1 } 1402 i = i + 1 1403 } 1404 o = wf_cat(hb, o, "</table><p>runs " as *u8) 1405 o = wf_catn(hb, o, cnt) 1406 o = wf_cat(hb, o, " &middot; done " as *u8) 1407 o = wf_catn(hb, o, d) 1408 o = wf_cat(hb, o, " &middot; failed " as *u8) 1409 o = wf_catn(hb, o, f) 1410 o = wf_cat(hb, o, " &middot; parked " as *u8) 1411 o = wf_catn(hb, o, w) 1412 o = wf_cat(hb, o, " &middot; running " as *u8) 1413 o = wf_catn(hb, o, ru) 1414 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) 1415 if wf_writeall(out, hb, o) != 0 { return 0 - 1 } 1416 return cnt 1417} 1418 1419func wf_selftest() -> i64 { 1420 p("=== NX-WFLOW-ENGINE SELFTEST (R1 keystone: multi-step durable runs, retry, ledger-dedup, crash-resume) ===\n" as *u8) 1421 var ok: i64 = 1 1422 1423 // fresh unique ledger under /tmp (getpid is broken on this backend -- use epoch seconds) 1424 let led: *u8 = sys_mmap(128) 1425 var lo: i64 = 0 1426 lo = wf_cat(led, lo, "/tmp/wflow_st_" as *u8) 1427 lo = wf_catn(led, lo, wf_now()) 1428 lo = wf_cat(led, lo, ".log" as *u8) 1429 led[lo] = 0 as u8 1430 1431 let flows: *i64 = sys_mmap(8 * 8) as *i64 1432 flows[0] = "f1|deal-stage|to=Negotiation" as *u8 as i64 1433 flows[1] = "f2|giving-received|-" as *u8 as i64 1434 flows[2] = "f3|ops-check|-" as *u8 as i64 1435 let steps: *i64 = sys_mmap(8 * 16) as *i64 1436 steps[0] = "f1|1|create-task|-|Prepare contract and pricing|1" as *u8 as i64 1437 steps[1] = "f1|2|send|card|Congrats on reaching Negotiation|2" as *u8 as i64 1438 steps[2] = "f1|3|notify|-|Deal advanced|1" as *u8 as i64 1439 steps[3] = "f2|1|probe-fail|-|3|4" as *u8 as i64 1440 steps[4] = "f2|2|update-field|-|status=thanked|1" as *u8 as i64 1441 steps[5] = "f3|1|probe-fail|-|9|2" as *u8 as i64 1442 steps[6] = "f3|2|notify|-|never reached|1" as *u8 as i64 1443 1444 let cx: *i64 = sys_mmap(32) as *i64 1445 cx[0] = steps as i64 1446 cx[1] = 7 1447 cx[2] = led as i64 1448 cx[3] = 0 1449 1450 // T1 whole-set validation, loud refusals 1451 let l1: i64 = wf_load(steps, 7) 1452 let l2: i64 = wf_load_flows(flows, 3) 1453 p(" T1 load steps=" as *u8); pn(l1); p(" flows=" as *u8); pn(l2); p("\n" as *u8) 1454 if l1 != 7 { ok = 0 } 1455 if l2 != 3 { ok = 0 } 1456 let bad1: *i64 = sys_mmap(16) as *i64 1457 bad1[0] = "f9|1|explode|-|boom|1" as *u8 as i64 1458 if wf_load(bad1, 1) != (0 - 1) { ok = 0 } 1459 let bad2: *i64 = sys_mmap(16) as *i64 1460 bad2[0] = "f9|1|send|fax|x|1" as *u8 as i64 1461 if wf_load(bad2, 1) != (0 - 1) { ok = 0 } 1462 let bad3: *i64 = sys_mmap(16) as *i64 1463 bad3[0] = "f9|1|notify|-|x|0" as *u8 as i64 1464 if wf_load(bad3, 1) != (0 - 1) { ok = 0 } 1465 1466 let ridE1:*WfOwnedText=wf_id_hex("E1","f1");let tokE1:*WfOwnedText=wf_id_tok(ridE1.data) 1467 let ridE2:*WfOwnedText=wf_id_hex("E2","f2");let tokE2:*WfOwnedText=wf_id_tok(ridE2.data) 1468 let ridE3:*WfOwnedText=wf_id_hex("E3","f3");let tokE3:*WfOwnedText=wf_id_tok(ridE3.data) 1469 // T2 fire -> 3-step run completes in order, DONE recorded 1470 let r1: i64 = wf_fire(cx, flows, 3, "deal-stage~deal=Riverside Contract~to=Negotiation~id=E1" as *u8) 1471 let szp: *i64 = sys_mmap(8) as *i64 1472 var buf: *u8 = wf_read_snapshot(led, szp) 1473 let okE1: i64 = wf_lines_with2(buf, szp[0], tokE1.data, "status=OK" as *u8) 1474 let dnE1: i64 = wf_lines_with2(buf, szp[0], tokE1.data, "status=DONE" as *u8) 1475 p(" T2 fire started=" as *u8); pn(r1); p(" okSteps=" as *u8); pn(okE1); p(" done=" as *u8); pn(dnE1); p("\n" as *u8) 1476 if r1 != 1 { ok = 0 } 1477 if okE1 != 3 { ok = 0 } 1478 if dnE1 != 1 { ok = 0 } 1479 1480 // T3 durable dedup: same event id never starts twice (survives across processes via the ledger) 1481 let r2: i64 = wf_fire(cx, flows, 3, "deal-stage~deal=Riverside Contract~to=Negotiation~id=E1" as *u8) 1482 buf = wf_read_snapshot(led, szp) 1483 let stE1: i64 = wf_lines_with2(buf, szp[0], tokE1.data, "status=START" as *u8) 1484 p(" T3 refire rc=" as *u8); pn(r2); p(" starts=" as *u8); pn(stE1); p("\n" as *u8) 1485 if r2 != (0 - 100) { ok = 0 } 1486 if stE1 != 1 { ok = 0 } 1487 1488 // T4 per-step retry: probe-fail 3 with max 4 succeeds on attempt 3 1489 let r3: i64 = wf_fire(cx, flows, 3, "giving-received~person=Rose~amount=500~id=E2" as *u8) 1490 buf = wf_read_snapshot(led, szp) 1491 let ok3: i64 = wf_lines_with2(buf, szp[0], tokE2.data, "status=OK att=3" as *u8) 1492 let atts: i64 = wf_lines_with2(buf, szp[0], tokE2.data, "status=ATT" as *u8) 1493 let dnE2: i64 = wf_lines_with2(buf, szp[0], tokE2.data, "status=DONE" as *u8) 1494 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) 1495 if r3 != 1 { ok = 0 } 1496 if ok3 != 1 { ok = 0 } 1497 if atts != 4 { ok = 0 } 1498 if dnE2 != 1 { ok = 0 } 1499 1500 // T5 exhausted retries -> FAILED at step, later steps never run 1501 let r4: i64 = wf_fire(cx, flows, 3, "ops-check~probe=edge~id=E3" as *u8) 1502 buf = wf_read_snapshot(led, szp) 1503 let fsE3: i64 = wf_lines_with2(buf, szp[0], tokE3.data, "status=FAILSTEP" as *u8) 1504 let flE3: i64 = wf_lines_with2(buf, szp[0], tokE3.data, "status=FAILED" as *u8) 1505 let s2E3: i64 = wf_lines_with2(buf, szp[0], tokE3.data, " step=2 " as *u8) 1506 let dnE3: i64 = wf_lines_with2(buf, szp[0], tokE3.data, "status=DONE" as *u8) 1507 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) 1508 if fsE3 != 1 { ok = 0 } 1509 if flE3 != 1 { ok = 0 } 1510 if s2E3 != 0 { ok = 0 } 1511 if dnE3 != 0 { ok = 0 } 1512 1513 // T6 THE CROWN -- durable crash-resume: synthetic run crashed after step 1; resume completes 2..3 1514 // WITHOUT re-executing step 1 (event-sourced replay, additive only) 1515 wf_emit(led, "E9.f1" as *u8, "f1" as *u8, 0, "START" as *u8, 0) 1516 wf_emit_var(led,"E9.f1","id","E9") 1517 wf_emit(led, "E9.f1" as *u8, "f1" as *u8, 1, "ATT" as *u8, 1) 1518 wf_emit(led, "E9.f1" as *u8, "f1" as *u8, 1, "OK" as *u8, 1) 1519 let rs: i64 = wf_resume(cx) 1520 buf = wf_read_snapshot(led, szp) 1521 let okE9: i64 = wf_lines_with2(buf, szp[0], " rid=E9.f1 " as *u8, "status=OK" as *u8) 1522 let ok1E9: i64 = wf_lines_with2(buf, szp[0], " rid=E9.f1 " as *u8, " step=1 status=OK" as *u8) 1523 let dnE9: i64 = wf_lines_with2(buf, szp[0], " rid=E9.f1 " as *u8, "status=DONE" as *u8) 1524 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) 1525 if rs != 1 { ok = 0 } 1526 if okE9 != 3 { ok = 0 } 1527 if ok1E9 != 1 { ok = 0 } 1528 if dnE9 != 1 { ok = 0 } 1529 1530 // T7 liar-kill negatives: unknown-flow in-flight run refuses loudly (no fabricated DONE); 1531 // nonsense status never appears 1532 wf_emit(led, "EX.zz" as *u8, "zz" as *u8, 0, "START" as *u8, 0) 1533 wf_emit_var(led,"EX.zz","id","EX") 1534 let rz: i64 = wf_resume(cx) 1535 buf = wf_read_snapshot(led, szp) 1536 let dnZZ: i64 = wf_lines_with2(buf, szp[0], " rid=EX.zz " as *u8, "status=DONE" as *u8) 1537 let ban: i64 = wf_lines_with2(buf, szp[0], "WFRUN" as *u8, "status=BANANA" as *u8) 1538 p(" T7 negctl resumeRc=" as *u8); pn(rz); p(" zzDone=" as *u8); pn(dnZZ); p(" nonsense=" as *u8); pn(ban); p("\n" as *u8) 1539 if rz != (0 - 1) { ok = 0 } 1540 if dnZZ != 0 { ok = 0 } 1541 if ban != 0 { ok = 0 } 1542 1543 p("NX-WFLOW-ENGINE-SELFTEST ledger=" as *u8); p(led) 1544 p(" runs: done=3 failed=1 dedup=1 resumed=1 " as *u8) 1545 if ok == 1 { p("verdict=GREEN\n" as *u8); return 0 } 1546 p("verdict=RED\n" as *u8) 1547 return 1 1548}