code wiki / _hdl_build / nx_toolatency.nx

nx_toolatency.nx source

↩ module page · 559 lines · 29225 B

1// nx_toolatency.nx -- LATENCY CENSUS FOR THE TOOL SURFACE: the evidence behind async-lane routing. 2// 3// WHY THIS EXISTS (2026-08-07). The 503 class is a LATENCY problem. The edge closes its window at 15s 4// (NX_SD2_BACKEND_TIMEOUT_S) and the synchronous lane is budgeted at 14s (TEA_EXEC_TIMEOUT_SYNC_MS), 5// so a call that outruns the budget becomes a 503 WHOSE WORK MAY STILL HAVE LANDED -- the caller is 6// told it failed for a row that exists. async_only_tools.conf fixes that by forcing a tool onto the 7// detached job lane, but its membership was a HAND-WRITTEN LIST of tools somebody noticed failing. 8// You cannot route by a latency you never measured, and until today the daemon logged WHAT ran and 9// WHETHER it worked but never HOW LONG. ta_actlog now emits dur_ms (TOTAL HANDLER TIME, the quantity 10// the edge window actually applies -- exec time alone would understate it). This organ is the 11// consumer that turns those rows into a routing decision, so the lane stops being folklore. 12// 13// TAIL-ANCHORED BY CONSTRUCTION. actlog.jrnl is APPEND-ONLY, so a head-anchored capped read keeps the 14// OLDEST bytes and silently drops the NEWEST -- exactly backwards for a gauge whose job is to notice 15// what got slow LATELY. Measured cost of that mistake in this same estate: nx_wirecensus read this 16// very ledger head-anchored and was blind to 89 invocation records. 17// 18// REFUSES rather than reporting GREEN over data it never read: no-slow-tools and no-measurements are 19// different facts and must not share a representation. 20// 21// DURABLE + ROSTERABLE (2026-08-27, 503 lane): every run appends ONE row to 22// knowledge/status/toolatency.log (its own file, one writer -- the rm_trend_log law), so two runs are 23// a TREND and a fired beat is distinguishable from a dead one. The row carries `unrouted=` -- the 24// over-budget tools still on the sync lane -- so the count travels with its worklist. Refusals write 25// a NODATA row too: an abstention that leaves no artifact is indistinguishable from a beat that never 26// fired. 27import "nx_syscalls.nx" 28import "nx_toolatency_observation_lib.nx" 29 30const TL_TAIL_CAP: i64 = 4194304 31const TL_MAXTOOLS: i64 = 512 32const TL_NAMELEN: i64 = 48 33const TL_SYNC_BUDGET_MS: i64 = 14000 34const TL_WARN_PERMIL: i64 = 500 35const TL_CONFCAP: i64 = 65536 36const TL_SEEK_SET: i64 = 0 37const TL_SEEK_END: i64 = 2 38const TL_EXIT_SHOULD: i64 = 4 39const TL_EXIT_NODATA: i64 = 5 40const TL_EXIT_OVER: i64 = 6 41// A single call past the window is an OUTLIER; a tool that breaches repeatedly is BROKEN for that 42// lane. Rerouting changes the caller contract, so it must answer to a recurring breach and never to 43// one slow sample. Measured case that forced this: nx_mgmt showed max_ms=14002 -- TWO MILLISECONDS 44// past the budget -- across 69 calls averaging 1663 ms, i.e. one outlier, while nishi_compare_regen 45// breached on BOTH of its 2 calls at an average of 14854 ms. Max alone cannot tell those apart. 46const TL_MIN_BREACH: i64 = 2 47// The beat's durable artifact (ONE WRITER, ONE LOG, ONE ROW) + the bounded worklist buffer for the 48// unrouted= field. 4096 covers ~150 tool names against a measured worst of 21 offenders; a fuller 49// buffer ANNOUNCES with a +MORE sentinel rather than truncating silently. 50const TL_JRNL: *u8 = "knowledge/status/toolatency.log" as *u8 51// THE RANK TABLE (2026-09-15). The tools API orders tools/list by the presented cap, then by THESE counts 52// (most-called first), then newest-registered first -- see nx_tools_api.nx ta_emit_tools, and the measured 53// reason: a seat's MCP client walked 20 pages of 64 and stopped, so the ~700 newest of 1,977 registered 54// tools were invisible to every seat. The path is TA_RANK_DIR + the plane's basename + TA_RANK_EXT for the 55// production plane knowledge/toolreg-, spelled here ONCE so the writer and the reader name the same file; 56// nx_tools_api_gate proves the reader on a fixture plane and never touches this file. 57const TL_RANK_PATH: *u8 = "knowledge/status/toolrank-toolreg-.tsv" as *u8 58const TL_RANK_TMP: *u8 = "knowledge/status/toolrank-toolreg-.tsv.tmp" as *u8 59const TL_RANK_ROW_EXTRA: i64 = 48 // per row beyond the name: two decimals (<=20 digits each), two tabs, a newline, slack 60const TL_CSV_CAP: i64 = 4096 61// ACTION THRESHOLDS -- the pre-declared accept rule separating ROUTE (change the caller contract) 62// from TOLERATE (watch; the live sync-promote lane covers the tail). Without a rate/avg dimension 63// the census goes PERMANENTLY RED on high-volume fast tools (measured 2026-08-27: nx_fs, 19 breaches 64// in 12,141 calls = 1.6 permil -- rerouting it would tax twelve thousand fast reads with a poll to 65// save nineteen slow ones), and a permanently red detector is one everyone learns to ignore. 66// CONF IS THE SSOT (knowledge/toolatency.conf, rows act_permil= / act_avg_ms=, reasoning lives 67// there); these are the rule-17 bootstrap defaults, live only while the conf is absent. 68const TL_CONF: *u8 = "knowledge/toolatency.conf" as *u8 69const TL_ACT_PERMIL_DFLT: i64 = 40 70const TL_ACT_AVG_MS_DFLT: i64 = 2000 71// AUTO-ROUTE (2026-09-02). The accept rule below was applied BY HAND three times (08-27, 08-28, 08-29): 72// each time a seat copied this census into async_only_tools.conf while the OVER-BUDGET tools kept 503ing 73// for the day in between (measured 2026-09-02: nx_swarm_gpu, nx_frontier_put and nx_frontier_board sat 74// RED and unrouted across 20 consecutive hourly rows). A RULE APPLIED IDENTICALLY THREE TIMES BY A HUMAN 75// IS A RULE THAT BELONGS IN THE PATH. Conf-gated: knowledge/toolatency.conf auto_route=1 arms it; the 76// compiled default is OFF so the beat stays byte-identical until the operator turns the row on. 77const TL_AUTO_ROUTE_DFLT: i64 = 0 78const TL_ASYNC_CONF: *u8 = "async_only_tools.conf" as *u8 79const TL_CONF_MODE: i64 = 420 // 0644, the mode every other append-only conf/journal in the estate uses 80 81func tw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 82func twe(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 } 83func twn(v: i64) -> i64 { 84 var m: i64 = v 85 if m < 0 { tw("-" as *u8); m = 0 - m } 86 let t: *u8 = sys_mmap(28) 87 var k: i64 = 0 88 if m == 0 { t[0] = 48 as u8; k = 1 } 89 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 90 let o: *u8 = sys_mmap(28) 91 var i: i64 = 0 92 while i < k { o[i] = t[k - 1 - i]; i = i + 1 } 93 sys_write(1, o, k) 94 sys_munmap(t, 28) 95 sys_munmap(o, 28) 96 return 0 97} 98 99// small bounded buffer cats, local ON PURPOSE: importing a string lib into a leaf census organ would 100// drag a module in for two 15-line helpers. Same shapes as ta_cat/ta_catn. 101func tl_cat(b: *u8, o: i64, s: *u8) -> i64 { 102 var i: i64 = 0 103 var p: i64 = o 104 while s[i] != (0 as u8) { b[p] = s[i]; p = p + 1; i = i + 1 } 105 return p 106} 107func tl_catn(b: *u8, o: i64, v: i64) -> i64 { 108 var m: i64 = v 109 var p: i64 = o 110 if m < 0 { b[p] = 45 as u8; p = p + 1; m = 0 - m } 111 let t: *u8 = sys_mmap(28) 112 var k: i64 = 0 113 if m == 0 { t[0] = 48 as u8; k = 1 } 114 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 115 var i: i64 = 0 116 while i < k { b[p + i] = t[k - 1 - i]; i = i + 1 } 117 sys_munmap(t, 28) 118 return p + k 119} 120// ONE WRITER, ONE LOG, ONE ROW (the rm_trend_log law, nx_resmon): the durable per-run artifact, so a 121// census whose verdict was never persisted stops being a private opinion and two rows become a trend. 122// CLOSED VOCABULARY (GREEN|AMBER|RED|NODATA) so no rollup ever meets a new dialect. unrouted= names 123// the over-budget-and-still-sync tools -- the row carries its own worklist, because a count without a 124// worklist is not actionable. An unwritable journal ANNOUNCES on stderr rather than vanishing. 125// auto_routed= (2026-09-02) rides BEFORE verdict= so verdict stays the LAST token (gv_last_line anchors by 126// position) and every key= scanner keeps working; it is what lets a reader of this journal tell "still 127// unrouted" from "routed on this very row", which the unrouted= list alone cannot. 128func tl_jrnl(rows: i64, measured: i64, ntools: i64, over: i64, atrisk: i64, routed: i64, unrouted: *u8, verdict: *u8) -> i64 { 129 let fd: i64 = sys_openat_append(TL_JRNL, MODE_0644) 130 if fd < 0 { 131 twe("NX-TOOLATENCY jrnl-unwritable: " as *u8) 132 twe(TL_JRNL) 133 twe(" -- row lost, said out loud\n" as *u8) 134 return 0 - 1 135 } 136 let b: *u8 = sys_mmap(TL_CSV_CAP + 256) 137 var o: i64 = tl_cat(b, 0, "ts=" as *u8) 138 o = tl_catn(b, o, sys_now_realtime_sec()) 139 o = tl_cat(b, o, " rows=" as *u8); o = tl_catn(b, o, rows) 140 o = tl_cat(b, o, " measured=" as *u8); o = tl_catn(b, o, measured) 141 o = tl_cat(b, o, " tools=" as *u8); o = tl_catn(b, o, ntools) 142 o = tl_cat(b, o, " over_budget=" as *u8); o = tl_catn(b, o, over) 143 o = tl_cat(b, o, " at_risk=" as *u8); o = tl_catn(b, o, atrisk) 144 o = tl_cat(b, o, " unrouted=" as *u8) 145 o = tl_cat(b, o, unrouted) 146 o = tl_cat(b, o, " auto_routed=" as *u8); o = tl_catn(b, o, routed) 147 o = tl_cat(b, o, " verdict=" as *u8) 148 o = tl_cat(b, o, verdict) 149 b[o] = 10 as u8 150 sys_write(fd, b, o + 1) 151 sys_close(fd) 152 return 0 153} 154 155// TAIL-ANCHORED read: when the ledger exceeds the window, seek so the window ENDS at EOF. 156func tl_read_tail(path: *u8, buf: *u8, cap: i64) -> i64 { 157 let fd: i64 = sys_openat_rd(path) 158 if fd < 0 { return 0 - 1 } 159 let sz: i64 = sys_lseek(fd, 0, TL_SEEK_END) 160 if sz > cap { sys_lseek(fd, sz - cap, TL_SEEK_SET) } else { sys_lseek(fd, 0, TL_SEEK_SET) } 161 let n: i64 = sys_read(fd, buf, cap) 162 sys_close(fd) 163 return n 164} 165 166func tl_eol(buf: *u8, n: i64, i: i64) -> i64 { 167 var e: i64 = i 168 var go: i64 = 1 169 while go == 1 { 170 if e >= n { go = 0 } else { if buf[e] == (10 as u8) { go = 0 } else { e = e + 1 } } 171 } 172 return e 173} 174 175// value following key= within [s,e). -1 = key absent, which means UNMEASURED and never 0. 176func tl_kv(buf: *u8, s: i64, e: i64, key: *u8) -> i64 { 177 var kl: i64 = 0 178 while key[kl] != (0 as u8) { kl = kl + 1 } 179 var i: i64 = s 180 while i + kl <= e { 181 var k: i64 = 0 182 var ok: i64 = 1 183 while k < kl { if buf[i + k] != key[k] { ok = 0; k = kl } else { k = k + 1 } } 184 if ok == 1 { 185 var j: i64 = i + kl 186 var v: i64 = 0 187 var any: i64 = 0 188 var go: i64 = 1 189 while go == 1 { 190 go = 0 191 if j < e { 192 let c: i64 = buf[j] as i64 193 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; j = j + 1; go = 1 } } 194 } 195 } 196 if any == 0 { return 0 - 1 } 197 return v 198 } 199 i = i + 1 200 } 201 return 0 - 1 202} 203 204// exact-line membership in async_only_tools.conf. CR-tolerant. MIRRORS THE ENFORCER: the daemon's 205// ta_name_in_lines (nx_tools_api.nx, seq1293) matches the WHOLE line against the tool name, so a 206// per-verb "NAME VERB" row can NEVER match there and is inert. This reporter used to also accept the 207// NAME-space prefix -- two readers of one conf with two grammars -- and so displayed lane=async for 208// tools the daemon demonstrably ran sync (measured 2026-08-27: the inert "nx_debt add" rows). 209// The reporter must speak the enforcer's grammar or it lies about lanes. 210func tl_on_lane(conf: *u8, n: i64, nm: *u8, nl: i64) -> i64 { 211 var i: i64 = 0 212 while i < n { 213 let e: i64 = tl_eol(conf, n, i) 214 var len: i64 = e - i 215 if len > 0 { if conf[i + len - 1] == (13 as u8) { len = len - 1 } } 216 if len >= nl { 217 var k: i64 = 0 218 var same: i64 = 1 219 while k < nl { if conf[i + k] != nm[k] { same = 0; k = nl } else { k = k + 1 } } 220 if same == 1 { 221 if len == nl { return 1 } 222 } 223 } 224 i = e + 1 225 } 226 return 0 227} 228 229// ---- AUTO-ROUTE writers (2026-09-02; see TL_AUTO_ROUTE_DFLT) -------------------------------------------- 230// One append-row per routed tool: a comment carrying the measurement that admitted it (the same shape the 231// hand-adopted 08-28/08-29 rows carry, so the conf stays adjudicable without re-running the census), then 232// the bare name on its OWN line -- the enforcer's grammar is exact-whole-line (ta_name_in_lines), so the 233// name must stand alone. Returns the new offset; returns `o` unchanged (nothing appended) when the buffer 234// cannot take a whole row, so a half-written row can never be emitted. 235func tl_auto_route_row(b: *u8, o: i64, nm: *u8, nl: i64, breaches: i64, calls: i64, avg: i64) -> i64 { 236 var p: i64 = o 237 if p + nl + nl + 160 >= TL_CONFCAP { return o } 238 p = tl_cat(b, p, "# AUTO-ROUTED " as *u8) 239 p = tl_catn(b, p, sys_now_realtime_sec()) 240 p = tl_cat(b, p, " by nx_toolatency under the standing accept rule (breaches>=2 AND (permil>=act_permil OR avg>=act_avg_ms)): " as *u8) 241 var k: i64 = 0 242 while k < nl { b[p] = nm[k]; p = p + 1; k = k + 1 } 243 p = tl_cat(b, p, "(b=" as *u8) 244 p = tl_catn(b, p, breaches) 245 b[p] = 47 as u8 246 p = p + 1 247 p = tl_catn(b, p, calls) 248 p = tl_cat(b, p, ",avg=" as *u8) 249 p = tl_catn(b, p, avg) 250 p = tl_cat(b, p, ")\n" as *u8) 251 k = 0 252 while k < nl { b[p] = nm[k]; p = p + 1; k = k + 1 } 253 b[p] = 10 as u8 254 p = p + 1 255 return p 256} 257// ONE open, ONE write, ONE close, after the census: the conf is hot-read per call by the daemon, so the 258// very next call of each routed tool is already on the job lane. An unwritable conf is ANNOUNCED with its 259// rc -- a silent no-op here would read on the next census as "still unrouted" with no cause. 260func tl_auto_route_apply(b: *u8, n: i64, routed: i64) -> i64 { 261 let fd: i64 = sys_openat_append(TL_ASYNC_CONF, TL_CONF_MODE) 262 if fd < 0 { 263 tw("NX-TOOLATENCY auto_route=1 but " as *u8); tw(TL_ASYNC_CONF); tw(" is UNWRITABLE rc=" as *u8); twn(fd); tw(" -- NOTHING routed; the tools above stay on the sync lane\n" as *u8) 264 return 0 - 1 265 } 266 let w: i64 = sys_write(fd, b, n) 267 sys_close(fd) 268 tw("NX-TOOLATENCY AUTO-ROUTED " as *u8); twn(routed); tw(" tool(s) -> " as *u8); tw(TL_ASYNC_CONF); tw(" (+" as *u8); twn(w); tw(" B, one append; the daemon hot-reads it, so the NEXT call of each is already on the job lane)\n" as *u8) 269 return w 270} 271 272// Write the rank table ATOMICALLY (temp + rename): every tool this census saw, most-called first, ties by 273// first-seen order. The tools API reads it once per tools/list request, and a torn file would reorder a 274// seat's surface mid-page, so the rename is the publish. Returns rows written; -1 when the write was refused 275// (the previous table then stands untouched -- a stale rank beats a torn one). 276func tl_write_rank(names: *u8, cnt: *i64, ntools: i64, asof: i64) -> i64 { 277 let rcap: i64 = TL_MAXTOOLS * (TL_NAMELEN + TL_RANK_ROW_EXTRA) 278 let rb: *u8 = sys_mmap(rcap + 16) 279 let done: *i64 = sys_mmap(8 * TL_MAXTOOLS + 16) as *i64 280 var o: i64 = tl_cat(rb, 0, "# name<TAB>calls<TAB>asof -- written by nx_toolatency from actlog.jrnl; read by nx_tools_api ta_emit_tools (tier 2 of the list order)\n" as *u8) 281 var rows: i64 = 0 282 var more: i64 = 1 283 while more == 1 { 284 var best: i64 = 0 - 1 285 var q: i64 = 0 286 while q < ntools { if done[q] == 0 { if best < 0 { best = q } else { if cnt[q] > cnt[best] { best = q } } } q = q + 1 } 287 if best < 0 { more = 0 } else { 288 done[best] = 1 289 let nmp: *u8 = ((names as i64) + best * TL_NAMELEN) as *u8 290 if o + TL_NAMELEN + TL_RANK_ROW_EXTRA >= rcap { more = 0 } else { 291 o = tl_cat(rb, o, nmp) 292 rb[o] = 9 as u8; o = o + 1 293 o = tl_catn(rb, o, cnt[best]) 294 rb[o] = 9 as u8; o = o + 1 295 o = tl_catn(rb, o, asof) 296 rb[o] = 10 as u8; o = o + 1 297 rows = rows + 1 298 } 299 } 300 } 301 let fd: i64 = sys_openat_wr(TL_RANK_TMP, TL_CONF_MODE) 302 if fd < 0 { return 0 - 1 } 303 sys_write(fd, rb, o) 304 sys_close(fd) 305 if sys_renameat(TL_RANK_TMP, TL_RANK_PATH) != 0 { return 0 - 1 } 306 return rows 307} 308 309func main(argc: i64, argv: *i64) -> i64 { 310 if argc > 1 { 311 let command: *u8=argv[1] as *u8 312 if to_eq(command,0,to_len(command),"observations" as *u8) == 1 { 313 if sys_ignore_sigpipe() != 0 { return TO_STATUS_OUTPUT } 314 return to_observations(argc,argv) 315 } 316 } 317 let buf: *u8 = sys_mmap(TL_TAIL_CAP + 16) 318 let n: i64 = tl_read_tail("knowledge/status/actlog.jrnl" as *u8, buf, TL_TAIL_CAP) 319 if n <= 0 { 320 twe("NX-TOOLATENCY REFUSED: knowledge/status/actlog.jrnl unreadable or empty -- refusing to report GREEN over a ledger it never read\n" as *u8) 321 tl_jrnl(0, 0, 0, 0, 0, 0, "-" as *u8, "NODATA" as *u8) 322 sys_exit(TL_EXIT_NODATA) 323 return TL_EXIT_NODATA 324 } 325 let names: *u8 = sys_mmap(TL_MAXTOOLS * TL_NAMELEN + 16) 326 let cnt: *i64 = sys_mmap(8 * TL_MAXTOOLS + 16) as *i64 327 let mx: *i64 = sys_mmap(8 * TL_MAXTOOLS + 16) as *i64 328 let sm: *i64 = sys_mmap(8 * TL_MAXTOOLS + 16) as *i64 329 let br: *i64 = sys_mmap(8 * TL_MAXTOOLS + 16) as *i64 330 var ntools: i64 = 0 331 var rows: i64 = 0 332 var measured: i64 = 0 333 334 var i: i64 = 0 335 if n >= TL_TAIL_CAP { i = tl_eol(buf, n, 0) + 1 } 336 337 while i < n { 338 let e: i64 = tl_eol(buf, n, i) 339 var t2: i64 = 0 - 1 340 var t3: i64 = 0 - 1 341 var seen: i64 = 0 342 var p: i64 = i 343 while p < e { 344 if buf[p] == (9 as u8) { 345 seen = seen + 1 346 if seen == 2 { t2 = p } 347 if seen == 3 { t3 = p } 348 } 349 p = p + 1 350 } 351 if t2 >= 0 { if t3 > t2 { 352 rows = rows + 1 353 let d: i64 = tl_kv(buf, i, e, "dur_ms=" as *u8) 354 if d >= 0 { 355 measured = measured + 1 356 var nl2: i64 = t3 - t2 - 1 357 if nl2 > TL_NAMELEN - 1 { nl2 = TL_NAMELEN - 1 } 358 var idx: i64 = 0 - 1 359 var q: i64 = 0 360 while q < ntools { 361 var k: i64 = 0 362 var same: i64 = 1 363 while k < nl2 { if names[q * TL_NAMELEN + k] != buf[t2 + 1 + k] { same = 0; k = nl2 } else { k = k + 1 } } 364 if same == 1 { if names[q * TL_NAMELEN + nl2] == (0 as u8) { idx = q; q = ntools } else { q = q + 1 } } else { q = q + 1 } 365 } 366 if idx < 0 { 367 if ntools < TL_MAXTOOLS { 368 idx = ntools 369 var k2: i64 = 0 370 while k2 < nl2 { names[idx * TL_NAMELEN + k2] = buf[t2 + 1 + k2]; k2 = k2 + 1 } 371 names[idx * TL_NAMELEN + nl2] = 0 as u8 372 cnt[idx] = 0 373 mx[idx] = 0 374 sm[idx] = 0 375 br[idx] = 0 376 ntools = ntools + 1 377 } 378 } 379 if idx >= 0 { 380 cnt[idx] = cnt[idx] + 1 381 sm[idx] = sm[idx] + d 382 if d > mx[idx] { mx[idx] = d } 383 if d >= TL_SYNC_BUDGET_MS { br[idx] = br[idx] + 1 } 384 } 385 } 386 } } 387 i = e + 1 388 } 389 390 let cf: *u8 = sys_mmap(TL_CONFCAP + 16) 391 var cn: i64 = 0 392 let cfd: i64 = sys_openat_rd("async_only_tools.conf" as *u8) 393 if cfd >= 0 { cn = sys_read(cfd, cf, TL_CONFCAP); sys_close(cfd) } 394 395 // action thresholds: conf first (SSOT), bootstrap defaults otherwise. tl_kv returns -1 for an 396 // absent key, and -1 must never become a threshold -- absent means default, decided per key. 397 var act_permil: i64 = TL_ACT_PERMIL_DFLT 398 var act_avg: i64 = TL_ACT_AVG_MS_DFLT 399 let tcf: *u8 = sys_mmap(TL_CONFCAP + 16) 400 var tcn: i64 = 0 401 let tfd: i64 = sys_openat_rd(TL_CONF) 402 if tfd >= 0 { tcn = sys_read(tfd, tcf, TL_CONFCAP); sys_close(tfd) } 403 if tcn > 0 { 404 let cpm: i64 = tl_kv(tcf, 0, tcn, "act_permil=" as *u8) 405 if cpm >= 0 { act_permil = cpm } 406 let cav: i64 = tl_kv(tcf, 0, tcn, "act_avg_ms=" as *u8) 407 if cav >= 0 { act_avg = cav } 408 } 409 // AUTO-ROUTE arm (see TL_AUTO_ROUTE_DFLT): conf row wins, compiled default OFF. tl_kv returns -1 for an 410 // absent key and -1 must never become a mode -- absent means default, decided here. 411 var auto_route: i64 = TL_AUTO_ROUTE_DFLT 412 if tcn > 0 { let car: i64 = tl_kv(tcf, 0, tcn, "auto_route=" as *u8); if car >= 0 { auto_route = car } } 413 let apb: *u8 = sys_mmap(TL_CONFCAP + 16) 414 var apo: i64 = 0 415 var routed: i64 = 0 416 417 let warn_ms: i64 = TL_SYNC_BUDGET_MS * TL_WARN_PERMIL / 1000 418 tw("=== nx_toolatency -- measured handler latency vs the sync budget ===\n" as *u8) 419 tw("rows=" as *u8); twn(rows) 420 tw(" measured=" as *u8); twn(measured) 421 tw(" tools=" as *u8); twn(ntools) 422 tw(" sync_budget_ms=" as *u8); twn(TL_SYNC_BUDGET_MS) 423 tw(" warn_ms=" as *u8); twn(warn_ms) 424 tw(" lane_conf_bytes=" as *u8); twn(cn) 425 tw(" act_permil=" as *u8); twn(act_permil) 426 tw(" act_avg_ms=" as *u8); twn(act_avg) 427 if tcn > 0 { tw(" act_src=conf" as *u8) } else { tw(" act_src=default" as *u8) } 428 tw("\n" as *u8) 429 430 if measured == 0 { 431 twe("NX-TOOLATENCY REFUSED: not one row carries dur_ms -- this ledger predates the telemetry, so GREEN here would mean UNMEASURED, not FAST\n" as *u8) 432 tl_jrnl(rows, 0, ntools, 0, 0, 0, "-" as *u8, "NODATA" as *u8) 433 sys_exit(TL_EXIT_NODATA) 434 return TL_EXIT_NODATA 435 } 436 437 // THREE TIERS, arrived at by being wrong twice and letting the data correct the ruler. 438 // v1 flagged everything past HALF the budget as SHOULD-ASYNC -- that conflates an ALERT 439 // threshold with an ACTION threshold, and rerouting CHANGES THE CALLER CONTRACT (the caller 440 // gets a job id instead of a result), so it must never fire on a tool that still works. 441 // v2 acted on MAX past the full window -- but max cannot tell a systematic breach from one bad 442 // sample. It immediately flagged nx_mgmt, the tool that drives every build and deploy, on a 443 // max of 14002 ms: TWO MILLISECONDS over, across 69 calls averaging 1663. Rerouting the whole 444 // management API on that would have been an overreaction caused by the ruler, not the tool. 445 // v3 counts BREACHES. nishi_compare_regen breached on both of its 2 calls (avg 14854) -- that is 446 // a tool that does not fit the lane. nx_mgmt breached once in 69 -- that is an outlier. 447 // OVER-BUDGET = repeated breaches, act. OUTLIER = crossed once, watch. AT-RISK = past half, watch. 448 // the journal row's worklist: over-budget tools still on the sync lane, comma-joined, bounded. 449 let unr: *u8 = sys_mmap(TL_CSV_CAP + 16) 450 var uo: i64 = 0 451 var utrunc: i64 = 0 452 unr[0] = 0 as u8 453 var over: i64 = 0 454 var atrisk: i64 = 0 455 var should: i64 = 0 456 var q2: i64 = 0 457 while q2 < ntools { 458 let nmp: *u8 = ((names as i64) + q2 * TL_NAMELEN) as *u8 459 var nl3: i64 = 0 460 while nmp[nl3] != (0 as u8) { nl3 = nl3 + 1 } 461 let onlane: i64 = tl_on_lane(cf, cn, nmp, nl3) 462 tw(" " as *u8); tw(nmp) 463 tw(" calls=" as *u8); twn(cnt[q2]) 464 tw(" max_ms=" as *u8); twn(mx[q2]) 465 tw(" avg_ms=" as *u8); twn(sm[q2] / cnt[q2]) 466 if onlane == 1 { tw(" lane=async" as *u8) } else { tw(" lane=sync" as *u8) } 467 tw(" breaches=" as *u8); twn(br[q2]) 468 if onlane == 0 { 469 // ROUTE vs TOLERATE: recurring breaches alone are not the action bar -- rerouting taxes 470 // EVERY call of a tool with a poll to save its slow tail, so the rule demands the tail be 471 // a real fraction of calls (act_permil) or the tool be slow even on average (act_avg_ms). 472 // Same rule, same values, that admitted the 2026-08-27 async_only rows: ruler and decision 473 // share arithmetic or they drift. 474 var act: i64 = 0 475 if br[q2] >= TL_MIN_BREACH { 476 if (br[q2] * 1000) / cnt[q2] >= act_permil { act = 1 } 477 if sm[q2] / cnt[q2] >= act_avg { act = 1 } 478 } 479 if act == 1 { 480 over = over + 1 481 should = should + 1 482 // THE REASON TRAVELS WITH THE COUNT. A worklist of bare names is one step short: the 483 // reader still has to re-run the whole census to learn WHY each name is on it, and the 484 // numbers are already in hand right here. MEASURED CASE THAT FORCED THIS (2026-08-27): 485 // the beat wrote `unrouted=nx_page_verify` and a hand-run minutes later showed that tool 486 // TOLERATED at 3/88 = 34 permil, just under the 40 bar -- the tail window slides, so a 487 // borderline tool oscillates across the bar and a bare name cannot say which side it was 488 // on when the row was written. Emitting name(b=<breaches>/<calls>,avg=<ms>) makes the row 489 // adjudicable on its own, with no re-run and no access to the census that produced it. 490 // The 24-byte headroom covers the widest reason this can emit (two 10-digit counts plus 491 // the punctuation); too little room marks utrunc rather than emitting a half-written one. 492 if uo + nl3 + 24 < TL_CSV_CAP { 493 if uo > 0 { unr[uo] = 44 as u8; uo = uo + 1 } 494 var uk: i64 = 0 495 while uk < nl3 { unr[uo] = nmp[uk]; uo = uo + 1; uk = uk + 1 } 496 uo = tl_cat(unr, uo, "(b=" as *u8) 497 uo = tl_catn(unr, uo, br[q2]) 498 unr[uo] = 47 as u8 499 uo = uo + 1 500 uo = tl_catn(unr, uo, cnt[q2]) 501 uo = tl_cat(unr, uo, ",avg=" as *u8) 502 uo = tl_catn(unr, uo, sm[q2] / cnt[q2]) 503 unr[uo] = 41 as u8 504 uo = uo + 1 505 unr[uo] = 0 as u8 506 } else { utrunc = 1 } 507 // AUTO-ROUTE: accumulate the row now, write ONCE after the loop (one open, one write, one close). 508 if auto_route == 1 { 509 let apo2: i64 = tl_auto_route_row(apb, apo, nmp, nl3, br[q2], cnt[q2], sm[q2] / cnt[q2]) 510 if apo2 > apo { apo = apo2; routed = routed + 1 } 511 } 512 tw(" *** OVER-BUDGET: breaches the sync window REPEATEDLY -- these calls 503 today, move to async_only_tools.conf" as *u8) 513 if auto_route == 1 { tw(" (AUTO-ROUTING NOW -- see the AUTO-ROUTED line below)" as *u8) } 514 tw(" ***" as *u8) 515 } else { if br[q2] >= TL_MIN_BREACH { 516 atrisk = atrisk + 1 517 tw(" *** TOLERATED: repeated breaches below the action bar (rate under act_permil, avg under act_avg_ms) -- high-volume fast tool, the sync-promote lane covers this tail; deliberately NOT rerouted ***" as *u8) 518 } else { if br[q2] > 0 { 519 atrisk = atrisk + 1 520 tw(" *** OUTLIER: crossed the window once -- watch it, do not change the caller contract on one sample ***" as *u8) 521 } else { 522 if mx[q2] >= warn_ms { 523 atrisk = atrisk + 1 524 tw(" *** AT-RISK: past half the sync budget -- watch, do not reroute yet (rerouting changes the caller contract) ***" as *u8) 525 } 526 } } } 527 } 528 tw("\n" as *u8) 529 q2 = q2 + 1 530 } 531 532 // RANK TABLE for the tools API list order (2026-09-15): published on every census run, whatever the verdict. 533 let rrows: i64 = tl_write_rank(names, cnt, ntools, sys_now_realtime_sec()) 534 tw("NX-TOOLATENCY rank_rows=" as *u8); twn(rrows); tw(" rank_path=" as *u8); tw(TL_RANK_PATH); tw("\n" as *u8) 535 // AUTO-ROUTE: the accumulated rows land in ONE append, after the census and before the verdict. 536 if routed > 0 { tl_auto_route_apply(apb, apo, routed) } 537 tw("NX-TOOLATENCY tools=" as *u8); twn(ntools) 538 tw(" over_budget=" as *u8); twn(over) 539 tw(" at_risk=" as *u8); twn(atrisk) 540 tw(" auto_route=" as *u8); twn(auto_route) 541 tw(" auto_routed=" as *u8); twn(routed) 542 if utrunc == 1 { uo = tl_cat(unr, uo, ",+MORE" as *u8); unr[uo] = 0 as u8 } 543 if uo == 0 { uo = tl_cat(unr, 0, "-" as *u8); unr[uo] = 0 as u8 } 544 if over > 0 { 545 tw(" verdict=RED (the OVER-BUDGET tools are 503ing now; a 503 there loses a response for work that may have landed)\n" as *u8) 546 tl_jrnl(rows, measured, ntools, over, atrisk, routed, unr, "RED" as *u8) 547 sys_exit(TL_EXIT_OVER) 548 return TL_EXIT_OVER 549 } 550 if atrisk > 0 { 551 tw(" verdict=AMBER (nothing is failing yet; the AT-RISK tools are past half the window and worth watching)\n" as *u8) 552 tl_jrnl(rows, measured, ntools, over, atrisk, routed, unr, "AMBER" as *u8) 553 sys_exit(TL_EXIT_SHOULD) 554 return TL_EXIT_SHOULD 555 } 556 tw(" verdict=GREEN (every measured tool sits inside half the sync budget, or is already on the job lane)\n" as *u8) 557 tl_jrnl(rows, measured, ntools, over, atrisk, routed, unr, "GREEN" as *u8) 558 return 0 559}