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}