nx_crawlmine.nx
buildroot/runtime/nx_crawlmine.nx
about
nx_crawlmine.nx -- MINE the crawl outcome log so the crawler improves against MEASURED loss.
WHY THIS EXISTS. Until R11 the crawler dropped pages silently: a Cloudflare interstitial, a 403, a
TLS failure and a genuinely empty host all ended the same way -- no line, no row, nothing to count.
The host-health streak then retired the host, which turns OUR capability gap into PERMANENT coverage
loss that looks like the web's fault. Operator 2026-08-06: "make sure success isnt accidental and
that we have an error log we are mining and improving our crawler on."
This is the mining half. It answers the only questions that can drive the next rung:
- what fraction of fetches actually succeed (the rate, not the anecdote)
- which failure CLASS dominates (botwall vs http vs transport) = what to build next
- which HOSTS we are losing, and to what = whether escalation is working per-host
A SUCCESS ROW IS AS LOAD-BEARING AS A FAILURE ROW: without `ok` counts there is no denominator, and
a failure count with no denominator is not a rate -- the same trap that once reported a fixed
crash-loop as live. That is why the crawler logs ok too.
2026-08-13 WHOLE-FILE + WINDOW. v1 read the FIRST 4MiB and reported that head as the population --
a silent cap: once the log outgrew it every run returned the same frozen numbers (rows=50427
forever) and the post-R12 window was unobservable by construction. Now the 4MiB buffer is a CHUNK,
the file streams to EOF, and coverage (bytes, rows, epoch span) is printed so a partial read can
never pass as a population again. [since-epoch] restricts TALLIES to rows at/after that epoch =
the windowed referee for before/after claims (ok_per_day, hostcap paid-fetch vs free-skip).
nx_crawlmine [log-path] [since-epoch]
license_tier: ORIGINAL expect_exit: 0
dependencies 1 imports · 0 importers
imports: nx_syscalls.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 27 | const CM_MAGIC_86400: i64 = 86400 |
| 29 | const CM_LOG: *u8 = "knowledge/status/crawl_outcomes.log" |
| 30 | const CM_CAP: i64 = 4194304 // CHUNK buffer size, not a population cap -- the read loop streams to EOF |
| 31 | const CM_MAXC: i64 = 16 // distinct classes tracked |
| 32 | const CM_MAXH: i64 = 256 // distinct hosts tracked |
| 33 | const CM_SLOT: i64 = 64 |
functions
| 35 | func cm_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } |
| 36 | func cm_n(v: i64) -> i64 |
| 43 | func cm_num(b: *u8, s: i64, e: i64) -> i64 |
| 53 | func cm_eqn(a: *u8, al: i64, b: *u8, bl: i64) -> i64 |
| 60 | func cm_slot(tab: *u8, n: *i64, maxn: i64, s: *u8, sl: i64) -> i64 |
| 81 | func cm_host(u: *u8, us: i64, ue: i64, hb: *u8) -> i64 called by 1: cm_region |
| 103 | func cm_region(buf: *u8, end: i64, since: i64, ctx: *i64, st: *i64) -> i64 |
| 171 | func main(argc: i64, argv: *i64) -> i64 |