code wiki / (root) / nx_tmplstale_lib.nx

nx_tmplstale_lib.nx source

↩ module page · 283 lines · 12490 B

1// nx_tmplstale_lib.nx -- THE CLERK DESK: template registry, document lineage and TEMPLATE STALENESS 2// over nx_docgen_lib (LP6 dg_tmpl_list / LP7 dg_assemble_lineage / LP8 dg_tmpl_stale on /compare/legalpractice). 3// The gap this closes: nx_docgen stores clauses and templates on the immutable plane and assembles a 4// content-addressed document, but NOTHING records which clause TEXT a document was built from, so a 5// revised clause silently leaves every earlier engagement letter and every template that references it 6// out of date. Here every template put snapshots the CID of each clause it references, and every 7// assembly writes a LINEAGE record (template, document CID, clause CIDs). Staleness is then a pure 8// comparison: current clause CID != recorded CID names the stale template or document. The output is a 9// WORKLIST (every stale row named), never a count; a listing that would not fit its buffer REFUSES 10// (-1) rather than truncating, because a short worklist reads as a finished desk. license_tier: ORIGINAL 11 12import "nx_docgen_lib.nx" 13 14const TS_CIDLEN: i64 = 69 15const TS_LINEBUF: i64 = 512 16const TS_KEYBUF: i64 = 128 17const TS_TEXTBUF: i64 = 8192 18const TS_NL: i64 = 10 19const TS_TAB: i64 = 9 20const TS_SP: i64 = 32 21const TS_COMMA: i64 = 44 22const TS_COLON: i64 = 58 23const TS_ZERO: i64 = 48 24const TS_NINE: i64 = 57 25const TS_CIDSHORT: i64 = 21 26const TS_ROWRESERVE: i64 = 24 27 28func ts_cat(dst: *u8, o: i64, s: *u8) -> i64 { return mt_catcopy(dst, o, s) } 29 30// copy src[a..b) into dst as a NUL-terminated string; returns length 31func ts_slice(src: *u8, a: i64, b: i64, dst: *u8) -> i64 { 32 var k: i64 = 0 33 var p: i64 = a 34 while p < b { dst[k] = src[p]; k = k + 1; p = p + 1 } 35 dst[k] = 0 as u8 36 return k 37} 38 39// CID of the CURRENT text of clause `id`, or -1 if the clause is unknown (fail-closed) 40func ts_clause_cid(prefix: *u8, id: *u8, cid: *u8) -> i64 { 41 let po: *i64 = sys_mmap(16) as *i64 42 let lo: *i64 = sys_mmap(16) as *i64 43 if reg_get(prefix, "clause:" as *u8, id, po, lo) != 1 { return 0 - 1 } 44 cid_of(po[0] as *u8, lo[0], cid) 45 return TS_CIDLEN 46} 47 48// walk the comma-separated clause csv and append "<clauseid>:<cid>\n" per clause into out[o..]; 49// returns the new offset or -1 on an unknown clause (fail-closed, mirrors dg_assemble) 50func ts_snapshot_clauses(prefix: *u8, csv: *u8, csvlen: i64, out: *u8, o0: i64) -> i64 { 51 var o: i64 = o0 52 var s: i64 = 0 53 let cidbuf: *u8 = sys_mmap(TS_KEYBUF) 54 let cid: *u8 = sys_mmap(TS_KEYBUF) 55 while s < csvlen { 56 var e: i64 = s 57 var done: i64 = 0 58 while done == 0 { 59 if e >= csvlen { done = 1 } 60 if done == 0 { if (csv[e] as i64) == TS_COMMA { done = 1 } else { e = e + 1 } } 61 } 62 ts_slice(csv, s, e, cidbuf) 63 if ts_clause_cid(prefix, cidbuf, cid) < 0 { return 0 - 1 } 64 o = ts_cat(out, o, cidbuf) 65 out[o] = TS_COLON as u8 66 o = o + 1 67 o = ts_cat(out, o, cid) 68 out[o] = TS_NL as u8 69 o = o + 1 70 s = e + 1 71 } 72 return o 73} 74 75// digits until a non-digit (the epoch line of a snapshot record) 76func ts_atoi(s: *u8) -> i64 { 77 var v: i64 = 0 78 var i: i64 = 0 79 while (s[i] as i64) >= TS_ZERO { 80 if (s[i] as i64) > TS_NINE { return v } 81 v = v * 10 + ((s[i] as i64) - TS_ZERO) 82 i = i + 1 83 } 84 return v 85} 86 87// LP6 write side: put a template AND snapshot the clause CIDs it references at this moment, stamped with 88// the effective epoch. Record under tmplsnap:<id> = "epoch\n<clauseid>:<cid>\n...". -1 = unknown clause. 89func ts_tmpl_put(prefix: *u8, id: *u8, csv: *u8, epoch: i64) -> i64 { 90 let snap: *u8 = sys_mmap(TS_TEXTBUF) 91 var o: i64 = mt_catn(snap, 0, epoch) 92 snap[o] = TS_NL as u8 93 o = o + 1 94 let no: i64 = ts_snapshot_clauses(prefix, csv, dg_strlen(csv), snap, o) 95 if no < 0 { return 0 - 1 } 96 if dg_tmpl_put(prefix, id, csv) < 0 { return 0 - 1 } 97 return reg_put(prefix, "tmplsnap:" as *u8, "tmplsnap:__idx__" as *u8, id, snap, no) 98} 99 100// LP6 read side: list every template as "<id>\t<version cid of the csv>\t<effective epoch>\n" into out. 101// Returns the number of templates, or -1 if the listing would not fit cap (REFUSED, nothing written). 102func ts_tmpl_list(prefix: *u8, out: *u8, cap: i64) -> i64 { 103 let bo: *i64 = sys_mmap(16) as *i64 104 let n: i64 = reg_index_read(prefix, "tmpl:__idx__" as *u8, bo) 105 let idx: *u8 = bo[0] as *u8 106 var count: i64 = 0 107 var o: i64 = 0 108 var ls: i64 = 0 109 var i: i64 = 0 110 let id: *u8 = sys_mmap(TS_KEYBUF) 111 let cid: *u8 = sys_mmap(TS_KEYBUF) 112 let po: *i64 = sys_mmap(16) as *i64 113 let lo: *i64 = sys_mmap(16) as *i64 114 while i <= n { 115 var eol: i64 = 0 116 if i == n { eol = 1 } else { if (idx[i] as i64) == TS_NL { eol = 1 } } 117 if eol == 1 { 118 if i > ls { 119 ts_slice(idx, ls, i, id) 120 if reg_get(prefix, "tmpl:" as *u8, id, po, lo) == 1 { 121 cid_of(po[0] as *u8, lo[0], cid) 122 var epoch: i64 = 0 123 let spo: *i64 = sys_mmap(16) as *i64 124 let slo: *i64 = sys_mmap(16) as *i64 125 if reg_get(prefix, "tmplsnap:" as *u8, id, spo, slo) == 1 { epoch = ts_atoi(spo[0] as *u8) } 126 // refuse rather than truncate: id + tab + cid + tab + 20 digits + newline 127 if o + TS_KEYBUF + TS_CIDLEN + TS_ROWRESERVE > cap { return 0 - 1 } 128 o = ts_cat(out, o, id) 129 out[o] = TS_TAB as u8 130 o = o + 1 131 o = ts_cat(out, o, cid) 132 out[o] = TS_TAB as u8 133 o = o + 1 134 o = mt_catn(out, o, epoch) 135 out[o] = TS_NL as u8 136 o = o + 1 137 count = count + 1 138 } 139 } 140 ls = i + 1 141 } 142 i = i + 1 143 } 144 out[o] = 0 as u8 145 return count 146} 147 148// LP7: assemble template `tmpl_id` for document `docid` and record its LINEAGE: 149// lineage:<docid> = "tmpl=<tmpl_id>\ndoc=<doc cid>\n<clauseid>:<clause cid>\n...". Returns the assembled 150// length (document in out) or -1 (unknown template/clause or unfilled field -- dg_assemble's contract). 151func ts_assemble_lineage(prefix: *u8, tmpl_id: *u8, docid: *u8, mergerec: *u8, mergelen: i64, out: *u8) -> i64 { 152 let n: i64 = dg_assemble(prefix, tmpl_id, mergerec, mergelen, out) 153 if n < 0 { return 0 - 1 } 154 let doccid: *u8 = sys_mmap(TS_KEYBUF) 155 cid_of(out, n, doccid) 156 let po: *i64 = sys_mmap(16) as *i64 157 let lo: *i64 = sys_mmap(16) as *i64 158 if reg_get(prefix, "tmpl:" as *u8, tmpl_id, po, lo) != 1 { return 0 - 1 } 159 let lin: *u8 = sys_mmap(TS_TEXTBUF) 160 var o: i64 = ts_cat(lin, 0, "tmpl=" as *u8) 161 o = ts_cat(lin, o, tmpl_id) 162 lin[o] = TS_NL as u8 163 o = o + 1 164 o = ts_cat(lin, o, "doc=" as *u8) 165 o = ts_cat(lin, o, doccid) 166 lin[o] = TS_NL as u8 167 o = o + 1 168 let no: i64 = ts_snapshot_clauses(prefix, po[0] as *u8, lo[0], lin, o) 169 if no < 0 { return 0 - 1 } 170 if reg_put(prefix, "lineage:" as *u8, "lineage:__idx__" as *u8, docid, lin, no) < 0 { return 0 - 1 } 171 return n 172} 173 174// scan a snapshot/lineage record body for "<clauseid>:<cid>" lines and append one worklist row per 175// clause whose CURRENT cid differs; returns the new offset, or -1 if the worklist would overflow cap. 176func ts_scan_stale(prefix: *u8, rec: *u8, reclen: i64, kind: *u8, subject: *u8, out: *u8, o0: i64, cap: i64, stale: *i64) -> i64 { 177 var o: i64 = o0 178 var ls: i64 = 0 179 var i: i64 = 0 180 let line: *u8 = sys_mmap(TS_LINEBUF) 181 let cidnow: *u8 = sys_mmap(TS_KEYBUF) 182 let cidwas: *u8 = sys_mmap(TS_KEYBUF) 183 let cl: *u8 = sys_mmap(TS_KEYBUF) 184 while i <= reclen { 185 var eol: i64 = 0 186 if i == reclen { eol = 1 } else { if (rec[i] as i64) == TS_NL { eol = 1 } } 187 if eol == 1 { 188 let ll: i64 = i - ls 189 if ll > 0 { if ll < TS_LINEBUF { 190 ts_slice(rec, ls, i, line) 191 // a clause line is "<id>:<cid>" -- find the colon 192 var c: i64 = 0 193 var colon: i64 = 0 - 1 194 while c < ll { if colon < 0 { if (line[c] as i64) == TS_COLON { colon = c } } c = c + 1 } 195 if colon > 0 { 196 ts_slice(line, 0, colon, cl) 197 ts_slice(line, colon + 1, ll, cidwas) 198 // only lines whose value is a cid (nxc1-...) are clause lines; tmpl=/doc= lines are skipped 199 if cidwas[0] == (110 as u8) { if cidwas[1] == (120 as u8) { if cidwas[2] == (99 as u8) { 200 var differs: i64 = 0 201 if ts_clause_cid(prefix, cl, cidnow) < 0 { differs = 1 } else { if mt_streq(cidnow, cidwas) != 1 { differs = 1 } } 202 if differs == 1 { 203 if o + TS_LINEBUF > cap { return 0 - 1 } 204 o = ts_cat(out, o, kind) 205 out[o] = TS_SP as u8 206 o = o + 1 207 o = ts_cat(out, o, subject) 208 o = ts_cat(out, o, " clause=" as *u8) 209 o = ts_cat(out, o, cl) 210 o = ts_cat(out, o, " was=" as *u8) 211 var z: i64 = 0 212 while z < TS_CIDSHORT { out[o] = cidwas[z]; o = o + 1; z = z + 1 } 213 o = ts_cat(out, o, " now=" as *u8) 214 if ts_clause_cid(prefix, cl, cidnow) < 0 { o = ts_cat(out, o, "CLAUSE-REMOVED" as *u8) } else { 215 z = 0 216 while z < TS_CIDSHORT { out[o] = cidnow[z]; o = o + 1; z = z + 1 } 217 } 218 out[o] = TS_NL as u8 219 o = o + 1 220 stale[0] = stale[0] + 1 221 } 222 } } } 223 } 224 } } 225 ls = i + 1 226 } 227 i = i + 1 228 } 229 return o 230} 231 232// walk a registry index (newline ids); for each id read <kp><id> and scan it. Returns new offset or -1. 233func ts_scan_index(prefix: *u8, kp: *u8, idxkey: *u8, kind: *u8, out: *u8, o0: i64, cap: i64, stale: *i64) -> i64 { 234 let bo: *i64 = sys_mmap(16) as *i64 235 let n: i64 = reg_index_read(prefix, idxkey, bo) 236 let idx: *u8 = bo[0] as *u8 237 var o: i64 = o0 238 var ls: i64 = 0 239 var i: i64 = 0 240 let id: *u8 = sys_mmap(TS_KEYBUF) 241 let po: *i64 = sys_mmap(16) as *i64 242 let lo: *i64 = sys_mmap(16) as *i64 243 while i <= n { 244 var eol: i64 = 0 245 if i == n { eol = 1 } else { if (idx[i] as i64) == TS_NL { eol = 1 } } 246 if eol == 1 { 247 if i > ls { 248 ts_slice(idx, ls, i, id) 249 if reg_get(prefix, kp, id, po, lo) == 1 { 250 let no: i64 = ts_scan_stale(prefix, po[0] as *u8, lo[0], kind, id, out, o, cap, stale) 251 if no < 0 { return 0 - 1 } 252 o = no 253 } 254 } 255 ls = i + 1 256 } 257 i = i + 1 258 } 259 return o 260} 261 262// LP8: the clerk worklist. Every template whose snapshot no longer matches the current clause text 263// ("TEMPLATE-STALE <id> clause=<c> was=<cid> now=<cid>") and every generated document whose lineage 264// carries a superseded clause ("DOC-STALE <docid> clause=<c> was=<cid> now=<cid>"). Returns the number of 265// stale rows written into out (0 = the desk is current), or -1 if the worklist would overflow cap. 266func dg_tmpl_stale(prefix: *u8, out: *u8, cap: i64) -> i64 { 267 let stale: *i64 = sys_mmap(16) as *i64 268 stale[0] = 0 269 var o: i64 = ts_scan_index(prefix, "tmplsnap:" as *u8, "tmplsnap:__idx__" as *u8, "TEMPLATE-STALE" as *u8, out, 0, cap, stale) 270 if o < 0 { return 0 - 1 } 271 o = ts_scan_index(prefix, "lineage:" as *u8, "lineage:__idx__" as *u8, "DOC-STALE" as *u8, out, o, cap, stale) 272 if o < 0 { return 0 - 1 } 273 out[o] = 0 as u8 274 return stale[0] 275} 276 277// LP6 contract symbol (the matrix watch names dg_tmpl_list): the list verb under its contract name. 278func dg_tmpl_list(prefix: *u8, out: *u8, cap: i64) -> i64 { return ts_tmpl_list(prefix, out, cap) } 279 280// LP7 contract symbol (the matrix watch names dg_assemble_lineage). 281func dg_assemble_lineage(prefix: *u8, tmpl_id: *u8, docid: *u8, mergerec: *u8, mergelen: i64, out: *u8) -> i64 { 282 return ts_assemble_lineage(prefix, tmpl_id, docid, mergerec, mergelen, out) 283}