code wiki / (root) / nx_crm_flow.nx

nx_crm_flow.nx source

↩ module page · 180 lines · 7421 B

1// nx_crm_flow.nx -- WORKFLOW AUTOMATION TRIGGERS (CRM census E8). The HubSpot/Salesforce-Flow model, sovereign + 2// rules-AS-DATA (law: data as data): a rule = `event|cond(k=v or -)|action|channel-or-dash|arg`; an event = 3// `kind~k=v~...~id=EN` (tilde fields; pipe is the rule separator). Engine: LOAD validates every rule up front 4// (unknown action / invalid send channel = LOUD -1, nothing half-loaded) -> FIRE matches rules to an event and 5// executes actions -- create-task | send (any nx_send channel: email/postcard/letter/card/gift) | notify | 6// update-field. IDEMPOTENT by event id (law 10): the same event id never fires twice (a dedup ledger the caller 7// holds). This is what wires the whole OS together: a deal hitting Negotiation creates the task; a gift received 8// queues a thank-you CARD through the mail arc. Argless = selftest gate. license_tier: ORIGINAL expect_exit: 0 9import "nx_send.nx" 10 11// tilde-field of s (events) 12func fl_tf(s: *u8, idx: i64, out: *u8, cap: i64) -> i64 { 13 var segi: i64 = 0 14 var t: i64 = 0 15 var i: i64 = 0 16 var found: i64 = 0 17 var go: i64 = 1 18 while go == 1 { 19 let c: i64 = s[i] as i64 20 if c == 0 { go = 0 } 21 if go == 1 { 22 if c == 126 { segi = segi + 1 } else { 23 if segi == idx { found = 1; if t < cap - 1 { out[t] = s[i]; t = t + 1 } } 24 } 25 i = i + 1 26 } 27 } 28 out[t] = 0 as u8 29 return found 30} 31func fl_has(hay: *u8, needle: *u8) -> i64 { 32 let n: i64 = slen(hay) 33 let m: i64 = slen(needle) 34 if m == 0 { return 0 } 35 var i: i64 = 0 36 while i + m <= n { 37 var k: i64 = 0 38 var hit: i64 = 1 39 while k < m { if hay[i+k] != needle[k] { hit = 0; k = m } else { k = k + 1 } } 40 if hit == 1 { return 1 } 41 i = i + 1 42 } 43 return 0 44} 45// extract the id=... value from an event 46func fl_evid(ev: *u8, out: *u8, cap: i64) -> i64 { 47 let n: i64 = slen(ev) 48 var i: i64 = 0 49 while i + 3 < n { 50 if ev[i] == (126 as u8) { if ev[i+1] == (105 as u8) { if ev[i+2] == (100 as u8) { if ev[i+3] == (61 as u8) { 51 var q: i64 = i + 4 52 var t: i64 = 0 53 while q < n { if ev[q] == (126 as u8) { break } if t < cap - 1 { out[t] = ev[q]; t = t + 1 } q = q + 1 } 54 out[t] = 0 as u8 55 return 1 56 } } } } 57 i = i + 1 58 } 59 out[0] = 0 as u8 60 return 0 61} 62func fl_action_ok(a: *u8) -> i64 { 63 if seq(a, "create-task" as *u8) == 1 { return 1 } 64 if seq(a, "send" as *u8) == 1 { return 1 } 65 if seq(a, "notify" as *u8) == 1 { return 1 } 66 if seq(a, "update-field" as *u8) == 1 { return 1 } 67 return 0 68} 69// validate a full ruleset up front; -1 on any bad rule (nothing half-loaded) 70func fl_load(rules: *i64, n: i64) -> i64 { 71 let a: *u8 = sys_mmap(64) 72 let ch: *u8 = sys_mmap(64) 73 var i: i64 = 0 74 while i < n { 75 let r: *u8 = rules[i] as *u8 76 pipe_field(r, 2, a, 64) 77 if fl_action_ok(a) == 0 { p("WORKFLOW load ERROR unknown action=" as *u8); p(a); p(" -- fail loud\n" as *u8); return 0 - 1 } 78 if seq(a, "send" as *u8) == 1 { 79 pipe_field(r, 3, ch, 64) 80 if sr_valid(ch) == 0 { p("WORKFLOW load ERROR bad send channel=" as *u8); p(ch); p(" -- fail loud\n" as *u8); return 0 - 1 } 81 } 82 i = i + 1 83 } 84 return n 85} 86// does rule match event? (event kind equal + cond k=v present, or cond '-') 87func fl_match(rule: *u8, ev: *u8) -> i64 { 88 let rk: *u8 = sys_mmap(128) 89 let ek: *u8 = sys_mmap(128) 90 pipe_field(rule, 0, rk, 128) 91 fl_tf(ev, 0, ek, 128) 92 if seq(rk, ek) == 0 { return 0 } 93 let cond: *u8 = sys_mmap(256) 94 pipe_field(rule, 1, cond, 256) 95 if cond[0] == (45 as u8) { return 1 } 96 if cond[0] == (0 as u8) { return 1 } 97 // require "~<cond>" in the event 98 let nb: *u8 = sys_mmap(300) 99 nb[0] = 126 as u8 100 var i: i64 = 0 101 while cond[i] != (0 as u8) { nb[i+1] = cond[i]; i = i + 1 } 102 nb[i+1] = 0 as u8 103 return fl_has(ev, nb) 104} 105// execute one matched rule's action 106func fl_exec(rule: *u8, evid: *u8) -> i64 { 107 let a: *u8 = sys_mmap(64) 108 let ch: *u8 = sys_mmap(64) 109 let arg: *u8 = sys_mmap(512) 110 pipe_field(rule, 2, a, 64) 111 pipe_field(rule, 3, ch, 64) 112 pipe_field(rule, 4, arg, 512) 113 p("WORKFLOW-ACTION event=" as *u8); p(evid); p(" action=" as *u8); p(a) 114 if seq(a, "send" as *u8) == 1 { p(" channel=" as *u8); p(ch); p(" route=" as *u8); p(sr_route(ch)) } 115 p(" arg=" as *u8); p(arg); p("\n" as *u8) 116 return 0 117} 118// fire an event against the ruleset with an id-dedup ledger. seen: ptr array; snp[0] = count (in/out). 119// returns actions fired, or -100 for a duplicate (skipped). 120func fl_fire(rules: *i64, n: i64, ev: *u8, seen: *i64, snp: *i64) -> i64 { 121 let idb: *u8 = sys_mmap(128) 122 fl_evid(ev, idb, 128) 123 var i: i64 = 0 124 while i < snp[0] { if seq(seen[i] as *u8, idb) == 1 { p("WORKFLOW dedup: event " as *u8); p(idb); p(" already processed -- skipped\n" as *u8); return 0 - 100 } i = i + 1 } 125 // remember the id (copy) 126 let dup: *u8 = sys_mmap(slen(idb) + 2) 127 var q: i64 = 0 128 while idb[q] != (0 as u8) { dup[q] = idb[q]; q = q + 1 } 129 dup[q] = 0 as u8 130 seen[snp[0]] = dup as i64 131 snp[0] = snp[0] + 1 132 var fired: i64 = 0 133 var r: i64 = 0 134 while r < n { 135 if fl_match(rules[r] as *u8, ev) == 1 { fl_exec(rules[r] as *u8, idb); fired = fired + 1 } 136 r = r + 1 137 } 138 return fired 139} 140 141func main() -> i64 { 142 p("=== NX-CRM-FLOW SELFTEST (WORKFLOW: rules-as-data, event triggers, idempotent, send-channel wired) ===\n" as *u8) 143 var ok: i64 = 1 144 let rules: *i64 = sys_mmap(8 * 8) as *i64 145 rules[0] = "deal-stage|to=Negotiation|create-task|-|Prepare contract and pricing" as *u8 as i64 146 rules[1] = "giving-received|-|send|card|Thank you for your generosity" as *u8 as i64 147 rules[2] = "entity-created|type=person|notify|-|New person added to the graph" as *u8 as i64 148 let loaded: i64 = fl_load(rules, 3) 149 p(" rules loaded=" as *u8); pn(loaded); p("\n" as *u8) 150 if loaded != 3 { ok = 0 } 151 152 let seen: *i64 = sys_mmap(8 * 32) as *i64 153 let snp: *i64 = sys_mmap(8) as *i64 154 snp[0] = 0 155 let f1: i64 = fl_fire(rules, 3, "deal-stage~deal=Riverside Contract~to=Negotiation~id=E1" as *u8, seen, snp) 156 let f2: i64 = fl_fire(rules, 3, "giving-received~person=Rose~amount=500~id=E2" as *u8, seen, snp) 157 let f3: i64 = fl_fire(rules, 3, "deal-stage~deal=Old Bid~to=Screen~id=E3" as *u8, seen, snp) 158 let f4: i64 = fl_fire(rules, 3, "deal-stage~deal=Riverside Contract~to=Negotiation~id=E1" as *u8, seen, snp) 159 let f5: i64 = fl_fire(rules, 3, "entity-created~type=person~name=Marcus~id=E5" as *u8, seen, snp) 160 if f1 != 1 { ok = 0 } 161 if f2 != 1 { ok = 0 } 162 if f3 != 0 { ok = 0 } 163 if f4 != (0 - 100) { ok = 0 } 164 if f5 != 1 { ok = 0 } 165 166 // bad rulesets are refused up front (loud) 167 let bad1: *i64 = sys_mmap(8 * 2) as *i64 168 bad1[0] = "deal-stage|-|explode|-|boom" as *u8 as i64 169 let b1: i64 = fl_load(bad1, 1) 170 let bad2: *i64 = sys_mmap(8 * 2) as *i64 171 bad2[0] = "giving-received|-|send|fax|x" as *u8 as i64 172 let b2: i64 = fl_load(bad2, 1) 173 if b1 != (0 - 1) { ok = 0 } 174 if b2 != (0 - 1) { ok = 0 } 175 176 p("NX-CRM-FLOW-SELFTEST fired=" as *u8); pn(f1 + f2 + f5); p(" nomatch=0 dedup=1 badloads=2 " as *u8) 177 if ok == 1 { p("verdict=GREEN\n" as *u8); return 0 } 178 p("verdict=RED\n" as *u8) 179 return 1 180}