code wiki / _hdl_build / nx_autonomy_meter.nx
nx_autonomy_meter.nx source
↩ module page · 215 lines · 11501 B
1// nx_autonomy_meter.nx -- X-AUT-002: the autonomy GRADE, MEASURED never asserted
2// (autonomy ladder spec 2026-06-11; declared W0 tutor-with-team scaffold -- and this
3// organ COUNTS ITSELF against the grade: it is tutor-authored, so A2 stays honest).
4// PLAIN LANGUAGE: how much of the work does the system do by itself? Four axes,
5// each 0..1000 permil, each from a ledger anyone can re-read:
6// A1 judged-DONE share : queue rows DONE carrying ||MARK= (machine-judged by
7// reconcile evidence) / all DONE rows. Hand-flipped DONEs drag the grade.
8// A2 emitter-authored share : ABSENT (named gap) -- registrations do not yet carry
9// an author= tag (row X-AUT-002a files it). Counted 0, never faked.
10// A3 daemon-run share : pulse runs fired by the autorun daemon (DAEMON-BEAT rows)
11// / all pulse runs (PULSE-RUN rows). Hand-run beats drag the grade.
12// A4 hands-off-heal share : ENG- defect rows DONE with MARK / all ENG- rows DONE.
13// Zero closed = 0 with a note, never a crash.
14// autonomy_permil = (A1+A2+A3+A4)/4 ; grade: <150 F, <500 D, <700 C, <850 B,
15// <900 A, >=900 S (the ladder's bars).
16// Durable: AUTMETER row -> knowledge/status/autonomy_meter.log. Exit 0 = measured
17// (any grade); exit 1 = could not read its world. argv[1]=queue argv[2]=pulselog
18// argv[3]=daemonlog argv[4]=outlog (gates use scratch).
19// license_tier: ORIGINAL
20import "nx_syscalls.nx"
21const K_MAGIC_1048576: i64 = 1048576
22const K_MAGIC_1048560: i64 = 1048560
23func _p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
24func _fp(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
25// ★FIXED 2026-08-07 -- EMITS THE SIGN. The old body did `if m<0 { m = 0-m }` and printed the
26// magnitude, so AM_AXIS_UNKNOWN (-1) rendered as "1". MEASURED: the first run of the corrected
27// meter published `a3_daemon_runs=1`, which reads as a real, terrible 1-permil score -- the exact
28// confusion the UNKNOWN sentinel was introduced to remove. The arithmetic was already right
29// (axes_known=3, permil=705); only the display lied.
30// A NUMBER PRINTER THAT DROPS THE SIGN MAKES A SENTINEL INDISTINGUISHABLE FROM A VALUE.
31// This body is copy-pasted across the organ family in two variants -- some DO write the '-'
32// (nx_intent_gate, nx_revive_gate) and some do not. Same name, same call sites, different truth.
33func _fn(fd: i64, v: i64) -> i64 {
34 let bb: *u8 = sys_mmap(32)
35 var m: i64 = v
36 var neg: i64 = 0
37 if m < 0 { m = 0 - m; neg = 1 }
38 let t: *u8 = sys_mmap(32)
39 var k: i64 = 0
40 if m == 0 { t[0] = 48 as u8; k = 1 }
41 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
42 var o: i64 = 0
43 if neg == 1 { bb[0] = 45 as u8; o = 1 }
44 var i: i64 = 0
45 while i < k { bb[o + i] = t[k - 1 - i]; i = i + 1 }
46 sys_write(fd, bb, k + o)
47 return 0
48}
49func am_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
50func am_read(path: *u8, buf: *u8, cap: i64) -> i64 {
51 let fd: i64 = sys_openat_rd(path)
52 if fd < 0 { return 0 }
53 var n: i64 = 0
54 var go: i64 = 1
55 while go == 1 { let base: i64 = buf as i64; let r: i64 = sys_read(fd, (base + n) as *u8, cap - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap { go = 0 } }
56 sys_close(fd)
57 return n
58}
59func am_slice_has(hay: *u8, a: i64, b: i64, pat: *u8) -> i64 {
60 let pl: i64 = am_len(pat)
61 if pl == 0 { return 0 }
62 var i: i64 = a
63 while i + pl <= b {
64 var k: i64 = 0
65 var hit: i64 = 1
66 while k < pl { if hay[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } }
67 if hit == 1 { return 1 }
68 i = i + 1
69 }
70 return 0
71}
72// count LINES holding p1 (and p2 if not the 1-byte sentinel '*')
73func am_lines2(buf: *u8, n: i64, p1: *u8, p2: *u8) -> i64 {
74 var cnt: i64 = 0
75 var i: i64 = 0
76 while i < n {
77 var le: i64 = i
78 var s: i64 = 1
79 while s == 1 { if le >= n { s = 0 } else { if buf[le] == (10 as u8) { s = 0 } else { le = le + 1 } } }
80 if am_slice_has(buf, i, le, p1) == 1 {
81 var two: i64 = 1
82 if p2[0] != (42 as u8) { two = am_slice_has(buf, i, le, p2) }
83 if two == 1 { cnt = cnt + 1 }
84 }
85 i = le + 1
86 }
87 return cnt
88}
89// Returns the axis in permil, or AM_AXIS_UNKNOWN (-1) when there is NO DATA to score.
90//
91// ★FIXED 2026-08-07. This used to `return 0` on an empty denominator, and main() averaged that 0
92// into a FIXED divisor of 4. So an axis whose ledger DOES NOT EXIST scored a hard zero and was
93// published as if it had been measured badly.
94// MEASURED: A3 reads knowledge/status/team_pulse.log and pulse_daemon.log -- BOTH ABSENT -- so a3
95// scored 0/0 -> 0 and dragged the headline down by 250 permil (a quarter of the range) while
96// looking exactly like evidence of poor daemon autonomy.
97// A MISSING LEDGER SCORING ZERO IS INDISTINGUISHABLE FROM A MEASURED ZERO.
98// This is the same defect as a vacuous gate, only inverted: there, silence scored as SUCCESS; here,
99// silence scores as FAILURE. Both are wrong for the one reason -- SILENCE IS NOT AN ANSWER.
100// Callers must exclude UNKNOWN axes from the average and DECLARE how many were scorable, so a
101// missing instrument reads as "3 of 4 axes measured", never as "we measured, and it was zero".
102const AM_AXIS_UNKNOWN: i64 = 0 - 1
103
104func am_axis(num: i64, den: i64) -> i64 {
105 if den <= 0 { return AM_AXIS_UNKNOWN }
106 // v2 small-N damping (X-AUT-002b, filed BEFORE the n=1 jump printed): one lucky
107 // heal must not print grade C -- axis_effective = axis * min(den,5) / 5
108 var dmin: i64 = den
109 if dmin > 5 { dmin = 5 }
110 return (((1000 * num) / den) * dmin) / 5
111}
112func am_grade(fd: i64, permil: i64) -> i64 {
113 if permil < 150 { _fp(fd, "F" as *u8); return 0 }
114 if permil < 500 { _fp(fd, "D" as *u8); return 0 }
115 if permil < 700 { _fp(fd, "C" as *u8); return 0 }
116 if permil < 850 { _fp(fd, "B" as *u8); return 0 }
117 if permil < 900 { _fp(fd, "A" as *u8); return 0 }
118 _fp(fd, "S" as *u8)
119 return 0
120}
121func main(argc: i64, argv: *i64) -> i64 {
122 var qp: *u8 = "knowledge/registry/assignment_queue.tsv" as *u8
123 var plp: *u8 = "knowledge/status/team_pulse.log" as *u8
124 var dlp: *u8 = "knowledge/status/pulse_daemon.log" as *u8
125 var olp: *u8 = "knowledge/status/autonomy_meter.log" as *u8
126 if argc >= 2 { qp = argv[1] as *u8 }
127 if argc >= 3 { plp = argv[2] as *u8 }
128 if argc >= 4 { dlp = argv[3] as *u8 }
129 if argc >= 5 { olp = argv[4] as *u8 }
130 _p("=== AUTONOMY METER: the grade is measured from ledgers, never asserted ===\n" as *u8)
131 let qb: *u8 = sys_mmap(K_MAGIC_1048576)
132 let qn: i64 = am_read(qp, qb, K_MAGIC_1048560)
133 if qn <= 0 { _p(" queue MISSING -- fail loud\n" as *u8); sys_exit(1); return 1 }
134 let pb: *u8 = sys_mmap(K_MAGIC_1048576)
135 let pn: i64 = am_read(plp, pb, K_MAGIC_1048560)
136 let db: *u8 = sys_mmap(K_MAGIC_1048576)
137 let dn: i64 = am_read(dlp, db, K_MAGIC_1048560)
138 let star: *u8 = "*" as *u8
139 // A1: DONE rows judged by MARK evidence / all DONE rows
140 let done_all: i64 = am_lines2(qb, qn, "\tDONE\t" as *u8, star)
141 let done_mark: i64 = am_lines2(qb, qn, "\tDONE\t" as *u8, "||MARK=" as *u8)
142 let a1: i64 = am_axis(done_mark, done_all)
143 // A2 v3: registrations carrying author= tags (the protocol starts 2026-06-12;
144 // untagged pre-protocol entries are excluded -- the share is over TAGGED only,
145 // and zero tagged entries reads 0 honestly, never ABSENT-faked-green)
146 let rb2: *u8 = sys_mmap(K_MAGIC_1048576)
147 let rn2: i64 = am_read("knowledge/status/cap_registry_durable.log" as *u8, rb2, K_MAGIC_1048560)
148 let auth_all: i64 = am_lines2(rb2, rn2, " author=" as *u8, star)
149 let auth_emit: i64 = am_lines2(rb2, rn2, " author=emitter" as *u8, star)
150 let a2: i64 = am_axis(auth_emit, auth_all)
151 // A3: daemon-fired pulse runs / all pulse runs
152 let runs_all: i64 = am_lines2(pb, pn, "PULSE-RUN " as *u8, star)
153 var runs_daemon: i64 = am_lines2(db, dn, "DAEMON-BEAT " as *u8, star)
154 if runs_daemon > runs_all { runs_daemon = runs_all }
155 let a3: i64 = am_axis(runs_daemon, runs_all)
156 // A4: ENG- defect rows healed hands-off (DONE with MARK) / ENG- rows DONE
157 let eng_done: i64 = am_lines2(qb, qn, "ENG-" as *u8, "\tDONE\t" as *u8)
158 let eng_mark: i64 = am_lines2(qb, qn, "ENG-" as *u8, "||MARK=" as *u8)
159 var eng_mark_done: i64 = eng_mark
160 if eng_done < eng_mark_done { eng_mark_done = eng_done }
161 let a4: i64 = am_axis(eng_mark_done, eng_done)
162 // Average ONLY the axes that could be scored. A fixed /4 divisor silently treats an absent
163 // instrument as a measured zero; this counts what was actually measurable and publishes the
164 // count, so a reader can tell a low grade from a partly-blind one.
165 var sum: i64 = 0
166 var known: i64 = 0
167 if a1 != AM_AXIS_UNKNOWN { sum = sum + a1; known = known + 1 }
168 if a2 != AM_AXIS_UNKNOWN { sum = sum + a2; known = known + 1 }
169 if a3 != AM_AXIS_UNKNOWN { sum = sum + a3; known = known + 1 }
170 if a4 != AM_AXIS_UNKNOWN { sum = sum + a4; known = known + 1 }
171 // Zero scorable axes is INSTRUMENT-BLIND, not a grade of 0. Refuse rather than publish.
172 if known == 0 {
173 _p(" AUTMETER INSTRUMENT-BLIND: no axis had any data -- refusing to publish a grade\n" as *u8)
174 let bfd: i64 = sys_openat_append(olp, 0x1a4)
175 if bfd >= 0 { _fp(bfd, "AUTMETER epoch=" as *u8); _fn(bfd, sys_now_realtime_sec()); _fp(bfd, " verdict=INSTRUMENT-BLIND axes_known=0 of 4 permil=NA\n" as *u8); sys_close(bfd) }
176 sys_exit(1); return 1
177 }
178 let permil: i64 = sum / known
179 let lfd: i64 = sys_openat_append(olp, 0x1a4)
180 if lfd < 0 { _p(" meter log open failed\n" as *u8); sys_exit(1); return 1 }
181 _fp(lfd, "AUTMETER epoch=" as *u8); _fn(lfd, sys_now_realtime_sec())
182 _fp(lfd, " a1_judged_done=" as *u8); _fn(lfd, a1)
183 _fp(lfd, " a2_emitter_authored=" as *u8); _fn(lfd, a2)
184 _fp(lfd, " n2=" as *u8); _fn(lfd, auth_all)
185 _fp(lfd, " a3_daemon_runs=" as *u8); _fn(lfd, a3)
186 _fp(lfd, " a4_handsoff_heals=" as *u8); _fn(lfd, a4)
187 _fp(lfd, " n1=" as *u8); _fn(lfd, done_all)
188 _fp(lfd, " n3=" as *u8); _fn(lfd, runs_all)
189 _fp(lfd, " n4=" as *u8); _fn(lfd, eng_done)
190 _fp(lfd, " done_rows=" as *u8); _fn(lfd, done_all)
191 _fp(lfd, " pulse_runs=" as *u8); _fn(lfd, runs_all)
192 _fp(lfd, " permil=" as *u8); _fn(lfd, permil)
193 // DECLARE the scorable-axis count. A grade averaged over 3 of 4 axes is a different claim from
194 // one averaged over 4, and a reader that cannot see the difference cannot tell a genuinely low
195 // score from a partly-blind instrument. -1 in any a<N> field above means THAT AXIS HAD NO DATA.
196 _fp(lfd, " axes_known=" as *u8); _fn(lfd, known)
197 _fp(lfd, " of_4 unscorable=" as *u8); _fn(lfd, 4 - known)
198 _fp(lfd, " grade=" as *u8); am_grade(lfd, permil)
199 if known < 4 { _fp(lfd, " verdict=PARTIAL" as *u8) }
200 _fp(lfd, "\n" as *u8)
201 sys_close(lfd)
202 _p(" AUTMETER permil=" as *u8)
203 let ob: *u8 = sys_mmap(28)
204 var m: i64 = permil
205 var k: i64 = 0
206 if m == 0 { ob[0] = 48 as u8; k = 1 }
207 while m > 0 { ob[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
208 var i: i64 = 0
209 let rb: *u8 = sys_mmap(28)
210 while i < k { rb[i] = ob[k-1-i]; i = i + 1 }
211 sys_write(1, rb, k)
212 _p(" (durable: autonomy_meter.log; the grade moves only when the ledgers move)\n" as *u8)
213 sys_exit(0)
214 return 0
215}