nx_logtail.nx source
↩ module page · 112 lines · 7519 B
1// nx_logtail.nx -- THE shared HONEST TAIL READER for append-only logs and journals. ONE copy, so the
2// corpus stops re-deriving a bounded read that silently keeps the WRONG END of the file.
3//
4// ***A BOUNDED READ OF AN APPEND-ONLY LOG THAT KEEPS THE HEAD IS NOT A SMALLER TRUTH, IT IS THE
5// OPPOSITE ONE.*** Freshness, last-state and terminal-closure all live at the TAIL, so a reader that
6// bounds its read from offset 0 reports THE PAST AS THE PRESENT -- and because the dropped fraction
7// grows with the file, IT FAILS TOWARD FALSE VERDICTS ON THE MOST ACTIVE LOGS FIRST, which are exactly
8// the ones anyone cares about. Filed as a class with 4 instances in debt 1786054115.
9//
10// LIFTED, NEVER COPIED (2026-08-06, ws=resume-surface-rearm). This body is not new code: it is the
11// reader already proven GREEN 12/12 inside nx_ws_kickoff_sync.nx, including a neg-control tooth that
12// FORCES truncation through a deliberately tiny window and asserts newest-kept AND oldest-dropped.
13// It is lifted here verbatim so the second and third consumers inherit the proof instead of re-earning
14// it. MEASURED cost of NOT having this: nx_ws_kickoff_sync read 262140 of a 1364805-byte journal (19.2%)
15// from offset 0 and never compared the count to the file size, which did not merely hide rows -- it
16// FABRICATED, because the closure and last-activity predicates test WITHIN the buffer, so lanes closed
17// past the cap read IN-FLIGHT FOREVER and every age was a false staleness age. Two seats then acted on
18// those numbers. nx_cron_watch had the same geometry in a different organ (head served as tail) and
19// produced a false 12.5-day dead-beat alarm that a second seat propagated.
20//
21// IMPORT COST IS DELIBERATELY THE MINIMUM POSSIBLE -- nx_syscalls.nx and nothing else -- per the law in
22// nx_itoa_lib.nx: WHEN A CORRECT PRIMITIVE IS RETYPED, MEASURE ITS IMPORT COST BEFORE BLAMING
23// DISCOVERABILITY, because people do not retype what is CHEAP to reach. That is also why this lib does
24// NOT format the envelope: every consumer already owns a catn (ks_catn, cw_catn, st_catn) and each emits
25// a different shape (text vs JSON), so formatting here would either force a format or drag in an emitter
26// and price the lib out of the very sites it exists to serve. The numbers are handed back as data.
27//
28// ENV CONTRACT -- caller passes a *i64 with at least LT_ENV_SLOTS slots (>=32 bytes):
29// env[0] = file_bytes EXACT total size, because the whole file is streamed. This is strictly more
30// than nx_fs can report: nx_fs honestly declares FILE-EXCEEDS-SCAN-WINDOW but
31// cannot say BY HOW MUCH, which is why bounding a journal by hand cost twelve
32// probes on 2026-08-06 (debt 1786054029).
33// env[1] = scanned bytes RETAINED in buf and safe to parse (always a whole number of records)
34// env[2] = dropped_oldest file_bytes - scanned
35// env[3] = truncated 0 or 1. THE CALLER MUST SURFACE THIS. A partial read reported as complete is
36// the defect this file exists to end; declare it or fail loud, never imply it.
37// license_tier: ORIGINAL No hw writes (Rule 26).
38import "nx_syscalls.nx"
39
40const LT_ENV_SLOTS: i64 = 4
41const LT_NL: i64 = 10
42
43// move len bytes down to offset 0 from `from`. Forward copy is safe here because dst < src always.
44func lt_shift_down(b: *u8, from: i64, len: i64) -> i64 {
45 var i: i64 = 0
46 while i < len { b[i] = b[from + i]; i = i + 1 }
47 return len
48}
49
50// Seek to the tail of `path`, RETAIN the newest <=cap bytes in buf, and declare the envelope in env.
51// Returns bytes retained, or -1 IF THE FILE CANNOT BE OPENED. That -1 is not decoration: ABSENT and
52// EMPTY are different facts and a reader that collapses them cannot report the difference. v1 returned 0
53// for both, which would have silently downgraded nx_govern_sweep's `verdict=ABSENT path=...` (exit 4) into
54// a GOVERNED verdict over zero bytes -- a missing supervisor log reported as a healthy one. gv_tail, the
55// hand-rolled copy being retired, already returned -1; that is the SECOND thing the known good got right
56// that this lib had to be corrected to match. env is zeroed in both cases, so a caller that ignores the
57// return still sees file_bytes=0 rather than a fabricated size.
58//
59// ***CORRECTION, 2026-08-06, SAME SESSION AS THIS FILE'S FIRST VERSION.*** v1 streamed the ENTIRE file
60// through a halving window and justified it in this very comment with "it needs no lseek/fstat helper".
61// THAT JUSTIFICATION WAS FALSE. sys_lseek has been in nx_syscalls all along, and nx_govern_sweep.gv_tail
62// -- one of the very hand-rolled copies this lib exists to absorb -- was already using
63// sys_lseek(fd,0,2) to size the file and seek straight to sz-win. I did not check before writing a
64// rationale, so I shipped a plausible-sounding reason for a slower design and put it in a SHARED header
65// where every future reader would have inherited it.
66// ***A DESIGN NOTE IS A CLAIM AND MUST BE CHECKED LIKE ONE. An unverified rationale in a shared lib does
67// not just mislead one author, it teaches the whole corpus.*** The retyped copy I set out to retire was
68// better than mine on the axis I rationalised away -- which is exactly why the doctrine is MATCH THE
69// KNOWN GOOD FIRST: it is also the thing most likely to correct you.
70// v2 COMPOSES both halves rather than picking a winner: THEIR seek (file_bytes in one syscall, and I/O
71// bounded by cap instead of by file size -- on the 1.37MB journal that is ~4x less reading, and unbounded
72// savings as any log grows) plus MINE (record-boundary trim so a torn first line can never be parsed as a
73// whole record, and a DECLARED envelope so a partial read can never pass as complete). Neither copy had
74// both. The gate is the oracle for this swap: same contract, better implementation, so 8/8 must still hold.
75func lt_read_tail(path: *u8, buf: *u8, cap: i64, env: *i64) -> i64 {
76 env[0] = 0; env[1] = 0; env[2] = 0; env[3] = 0
77 let fd: i64 = sys_openat_rd(path)
78 if fd < 0 { return 0 - 1 }
79 let sz: i64 = sys_lseek(fd, 0, 2)
80 if sz <= 0 { sys_close(fd); return 0 }
81 var start: i64 = 0
82 var trunc: i64 = 0
83 if sz > cap { start = sz - cap; trunc = 1 }
84 sys_lseek(fd, start, 0)
85 var n: i64 = 0
86 var go: i64 = 1
87 while go == 1 {
88 let r: i64 = sys_read(fd, ((buf as i64) + n) as *u8, cap - n)
89 if r <= 0 { go = 0 } else { n = n + r }
90 if n >= cap { go = 0 }
91 }
92 sys_close(fd)
93 // A tail window that started mid-file almost certainly begins MID-RECORD. Advance past the first
94 // newline so the caller can never parse a half record as a whole one -- a torn first line is not a
95 // smaller truth either. env[2] therefore counts BOTH the pre-window bytes and this trimmed remnant,
96 // which is why the gate asserts dropped == file_bytes - scanned rather than == start.
97 if trunc == 1 {
98 var f: i64 = 0
99 var s: i64 = 1
100 while s == 1 { if f >= n { s = 0 } else { if buf[f] == (LT_NL as u8) { s = 0 } else { f = f + 1 } } }
101 if f < n { n = lt_shift_down(buf, f + 1, n - (f + 1)) }
102 }
103 env[0] = sz; env[1] = n; env[2] = sz - n; env[3] = trunc
104 return n
105}
106
107// 1 iff the whole file was retained. Provided so a caller can FAIL LOUD in one line rather than
108// forgetting env[3] exists -- the omission that made every instance of this class silent.
109func lt_complete(env: *i64) -> i64 {
110 if env[3] == 0 { return 1 }
111 return 0
112}