nx_rungclose_lib.nx source
↩ module page · 1293 lines · 64244 B
1// nx_rungclose_lib.nx -- THE CLOSE RULER (ecosystem EC57): ONE stage per rung, MEASURED from evidence the estate already
2// keeps and NEVER SIGNED -- not by a seat, not by the operator.
3// OPERATOR 2026-09-17 (three messages): completion needs NO manual sign-off, theirs or a seat's typed done row; everything
4// goes all the way done MECHANICALLY; maturity is TESTED STAGES -- alpha, beta, production; issues DEMOTE and flag REWORK.
5// v1 (the same day) read a typed done row as ATTESTED and a typed DONE frame as CLOSED, so the 27 rungs that were built,
6// live and wired sat one signature short of done for good, and a door that closed on a signature closed 19 rungs on a
7// SLICE the same afternoon (retracted within the hour). v2 reads evidence only:
8// links, in the order a rung climbs them (each 1 holds | 0 fails | -1 could not be observed):
9// JOURNAL the board's journal has a row for the rung (somebody started)
10// BUILT the contract symbol is present in its organ (a rung that declares no contract holds by declaration)
11// LIVE the capability answers on the callable surface: the treat row's MCP tool, else the contract organ's own
12// tool (its basename, a lib's basename less _lib), on the allowlist
13// TESTED the gate that proves the subject (resolved by nx_organ_ship_lib, the ship loop's own resolver) has a GREEN
14// roster row at or after the rung's newest land, in the production journal or in the journal of any roster
15// (nx_rosterconf_lib pair) whose conf lists the gate -- a slow beat's evidence is read where that beat writes it
16// WIRED something runs it unattended: the treat row is COMPLETE, else the execution-surface census puts it on a
17// clock, cron, daemon, roster, plan or fork surface -- a seat's MCP call alone is not wired
18// OPERATED its beat HAS run it: an unattended run at or after the newest land and inside the freshness margin, read
19// from the clock's own actlog rows or the roster journal (a process killed by a signal did not run)
20// PAGE the published page is at least as new as the newest land
21// stages: OPEN, STARTED, BUILT, LIVE, ALPHA (tested), BETA (wired), OPERATED, PRODUCTION (published)
22// ISSUES DEMOTE, each class with its own ceiling; three of them flag rework:
23// RED the gate's newest roster row is RED ceiling LIVE rework
24// RETRACT a retract row is newer than the newest land ceiling LIVE rework
25// FLAKY the verdict turned twice or more since the land, the last turn inside the margin ceiling ALPHA rework
26// STALE the roster has not run the gate inside the margin ceiling ALPHA the harness's defect, not the rung's
27// stage = the highest word whose links ALL hold in that order, lowered to the lowest ceiling a present issue sets; the
28// WEAKEST LINK is the first that does not hold (or the issue that capped it), BY NAME, with the rule that decided it and
29// the remedy that moves it. A link that could not be observed stops the climb exactly like a failed one and is NAMED
30// unobservable: I could not look is never it is broken, and never it is fine.
31// Pure: every function takes buffers and numbers, so a gate drives it in-process on planted rows.
32// license_tier: ORIGINAL No hw writes (Rule 26).
33import "nx_syscalls.nx"
34import "nx_costest_lib.nx"
35
36const RGC_I64: i64 = 8
37const RGC_L_JOURNAL: i64 = 0
38const RGC_L_BUILT: i64 = 1
39const RGC_L_LIVE: i64 = 2
40const RGC_L_TESTED: i64 = 3
41const RGC_L_WIRED: i64 = 4
42const RGC_L_OPERATED: i64 = 5
43const RGC_L_PAGE: i64 = 6
44const RGC_LINKS: i64 = 7
45const RGC_HOLDS: i64 = 1
46const RGC_FAILS: i64 = 0
47const RGC_UNOBS: i64 = 0 - 1
48const RGC_ST_NORUNG: i64 = 0
49const RGC_ST_OPEN: i64 = 1 // declared, no link holds; OPEN plus the count of links that hold in order is the stage
50const RGC_ST_STARTED: i64 = 2
51const RGC_ST_BUILT: i64 = 3
52const RGC_ST_LIVE: i64 = 4
53const RGC_ST_ALPHA: i64 = 5
54const RGC_ST_BETA: i64 = 6
55const RGC_ST_OPERATED: i64 = 7
56const RGC_ST_PRODUCTION: i64 = 8
57const RGC_STATES: i64 = 9
58// The v1 names (WIRED, LANDED, PUBLISHED, CLOSED; the LANDED and FRAME links) were RETIRED 2026-09-18 once nx_seat's T23 named
59// the v2 words: measured over both runtime roots, whole tree, before the deletion, nothing but this block and one gate tooth
60// that pinned their positions named any of them.
61const RGC_F_DECLARED: i64 = 0 // journal facts of ONE rung (rgc_journal)
62const RGC_F_ROWS: i64 = 1
63const RGC_F_LAND: i64 = 2
64const RGC_F_RETRACT: i64 = 3
65const RGC_F_SYM_O: i64 = 4
66const RGC_F_SYM_L: i64 = 5
67const RGC_F_COST: i64 = 6
68const RGC_F_DONE: i64 = 7 // the newest done row: a NOTE since v2, read by nothing that decides a stage
69const RGC_F_LAST: i64 = 8 // the newest journal row of ANY kind: when the rung was last touched
70const RGC_F_DECLS: i64 = 9 // how many rung rows DECLARE this id: more than one and every row that names it is ambiguous
71const RGC_F_SLOTS: i64 = 10
72const RGC_RUNG_F_SYMBOL: i64 = 3 // rung|id|title|symbol|note|kind|cost|deps
73const RGC_MX_F_ORGAN: i64 = 1 // label|organ|symbol|exceed|c1..c4|note
74const RGC_MX_F_SYMBOL: i64 = 2
75const RGC_MX_MIN_NF: i64 = 3
76const RGC_ABSENT_PFX: *u8 = "_ABSENT_:"
77const RGC_KIND_DONE: *u8 = "done" // a journal kind the boards render; since v2 it decides nothing
78const RGC_CH_TAB: i64 = 9
79const RGC_CH_CR: i64 = 13
80const RGC_CH_SP: i64 = 32
81const RGC_CH_SLASH: i64 = 47
82const RGC_CH_DOT: i64 = 46
83const RGC_CH_MINUS: i64 = 45
84const RGC_CH_COMMA: i64 = 44
85const RGC_CH_GT: i64 = 62
86
87// ---- why: the rule that decided a link, printed beside it, and the remedy that moves it ----------------------------------
88const RGC_W_NONE: i64 = 0
89const RGC_W_ROWS: i64 = 1 // JOURNAL holds: rows name the rung
90const RGC_W_NOROWS: i64 = 2 // JOURNAL fails
91const RGC_W_SYMBOL: i64 = 3 // BUILT holds: the symbol ruler found the contract
92const RGC_W_DECLARED: i64 = 4 // BUILT holds: the rung declares no contract
93const RGC_W_DARK: i64 = 5 // BUILT fails: the organ was read (or is unreadable) and the symbol is not declared
94const RGC_W_NOROW: i64 = 6 // BUILT unobservable: no matrix row names the contract
95const RGC_W_TREAT: i64 = 7 // LIVE/WIRED hold through the treat row's declaration, verified
96const RGC_W_TOOL: i64 = 8 // LIVE holds: the contract organ's own tool is on the allowlist
97const RGC_W_NOCONTRACT: i64 = 9 // LIVE fails: no contract and no treat row names a tool
98const RGC_W_UNREGISTERED: i64 = 10 // LIVE fails: the subject tool is not on the allowlist
99const RGC_W_GREEN: i64 = 11 // TESTED holds
100const RGC_W_NOGATE: i64 = 12 // TESTED fails: no gate resolves for the subject
101const RGC_W_UNROSTERED: i64 = 13 // TESTED fails: the gate resolves and is not on the roster
102const RGC_W_NOGREEN: i64 = 14 // TESTED fails: rostered, and no GREEN row at or after the land
103const RGC_W_SURFACE: i64 = 15 // WIRED holds: the census puts it on an unattended surface
104const RGC_W_MCPONLY: i64 = 16 // WIRED fails: only a seat's MCP call has run it
105const RGC_W_NOSURFACE: i64 = 17 // WIRED fails: nothing runs it
106const RGC_W_NOTINCENSUS: i64 = 18 // WIRED unobservable: the surface census has no row for it
107const RGC_W_RUN: i64 = 19 // OPERATED holds
108const RGC_W_NORUN: i64 = 20 // OPERATED fails: its beat has not run it since the land inside the margin
109const RGC_W_BLIND: i64 = 21 // OPERATED unobservable: its surface keeps no per-run witness this ruler reads
110const RGC_W_UNREADABLE: i64 = 22 // the evidence the link reads could not be read
111const RGC_W_PAGE: i64 = 23 // PAGE holds
112const RGC_W_PAGEOLD: i64 = 24 // PAGE fails: the page is older than the land
113const RGC_WHYS: i64 = 25
114
115func rgc_why_word(w: i64) -> *u8 {
116 if w == RGC_W_ROWS { return "journal-rows" as *u8 }
117 if w == RGC_W_NOROWS { return "no-journal-row" as *u8 }
118 if w == RGC_W_SYMBOL { return "symbol-present" as *u8 }
119 if w == RGC_W_DECLARED { return "no-contract-declared" as *u8 }
120 if w == RGC_W_DARK { return "symbol-absent" as *u8 }
121 if w == RGC_W_NOROW { return "no-matrix-row" as *u8 }
122 if w == RGC_W_TREAT { return "treat-row" as *u8 }
123 if w == RGC_W_TOOL { return "organ-tool-on-allowlist" as *u8 }
124 if w == RGC_W_NOCONTRACT { return "no-subject" as *u8 }
125 if w == RGC_W_UNREGISTERED { return "not-on-allowlist" as *u8 }
126 if w == RGC_W_GREEN { return "roster-green-since-land" as *u8 }
127 if w == RGC_W_NOGATE { return "no-gate-resolves" as *u8 }
128 if w == RGC_W_UNROSTERED { return "gate-not-rostered" as *u8 }
129 if w == RGC_W_NOGREEN { return "no-green-since-land" as *u8 }
130 if w == RGC_W_SURFACE { return "unattended-surface" as *u8 }
131 if w == RGC_W_MCPONLY { return "mcp-only" as *u8 }
132 if w == RGC_W_NOSURFACE { return "no-surface" as *u8 }
133 if w == RGC_W_NOTINCENSUS { return "not-in-surface-census" as *u8 }
134 if w == RGC_W_RUN { return "run-since-land" as *u8 }
135 if w == RGC_W_NORUN { return "no-run-since-land" as *u8 }
136 if w == RGC_W_BLIND { return "no-per-run-witness" as *u8 }
137 if w == RGC_W_UNREADABLE { return "evidence-unreadable" as *u8 }
138 if w == RGC_W_PAGE { return "page-current" as *u8 }
139 if w == RGC_W_PAGEOLD { return "page-older-than-land" as *u8 }
140 return "-" as *u8
141}
142
143// the remedy, one sentence each: a refusal names what moves it
144func rgc_why_need(w: i64) -> *u8 {
145 if w == RGC_W_NOROWS { return "no journal row names this rung: nobody has started it" as *u8 }
146 if w == RGC_W_DARK { return "the contract symbol is not declared in its organ: build it under the contracted name, or repoint the rung and its matrix row" as *u8 }
147 if w == RGC_W_NOROW { return "no matrix row names this rung's contract symbol, so nothing can measure it: add the watch row (label, organ path, _ABSENT_:<symbol>) to the board's matrix" as *u8 }
148 if w == RGC_W_NOCONTRACT { return "the rung names no contract symbol and no treat row names a tool, so there is no subject to test: give it an organ:symbol contract (a gate can be the contract) or a treat row" as *u8 }
149 if w == RGC_W_UNREGISTERED { return "the subject tool is not callable: promote it and register it (/api/tools/register), or declare the tool that carries it in a treat row" as *u8 }
150 if w == RGC_W_NOGATE { return "no gate resolves for the subject: build <organ>_gate, or declare the gate that proves it in knowledge/organ_gate.conf" as *u8 }
151 if w == RGC_W_UNROSTERED { return "the gate exists and is a row of no roster conf a clock beat runs (every one is listed after this remedy): admit it -- nx_gate_roster_run trial, then admit into the conf whose deadline fits it" as *u8 }
152 if w == RGC_W_NOGREEN { return "the roster has not run the gate GREEN since the rung last landed: the daily roster beat will; a RED row means the subject needs rework" as *u8 }
153 if w == RGC_W_MCPONLY { return "only a seat's MCP call runs it: give it an agent -- a clock row, a seeded plan a clock row runs, or the roster" as *u8 }
154 if w == RGC_W_NOSURFACE { return "nothing runs it: wire it to a clock row, a seeded plan or the roster" as *u8 }
155 if w == RGC_W_NOTINCENSUS { return "the execution-surface census has no row for it (the census predates its promote, or its elf name differs): the next nx_execsurface beat reads it" as *u8 }
156 if w == RGC_W_NORUN { return "its beat has not run it since the rung last landed, inside the freshness margin: check the clock row fires (nx_clockjob get) and exits by itself" as *u8 }
157 if w == RGC_W_BLIND { return "its surface keeps no per-run witness this ruler reads (a plan step, a daemon, a cron row or a fork): name its clock row as the treat row's agent" as *u8 }
158 if w == RGC_W_UNREADABLE { return "the evidence this link reads could not be read: the link is unobservable until it can be" as *u8 }
159 if w == RGC_W_PAGEOLD { return "the published page is older than the rung's newest land: republish the board" as *u8 }
160 return "-" as *u8
161}
162
163// the journal facts of one rung: is it declared, how many journal rows name it, its newest land and newest retract,
164// its contract symbol span and its declared cost. Returns declared (0 or 1).
165func rgc_journal(plan: *u8, pn: i64, rung: *u8, f: *i64) -> i64 {
166 let off: *i64 = sys_mmap(RGC_I64) as *i64
167 var k: i64 = 0
168 while k < RGC_F_SLOTS { f[k] = 0; k = k + 1 }
169 f[RGC_F_LAND] = CES_NONE
170 f[RGC_F_RETRACT] = CES_NONE
171 f[RGC_F_DONE] = CES_NONE
172 f[RGC_F_LAST] = CES_NONE
173 var p: i64 = 0
174 while p < pn {
175 let e: i64 = ces_line_end(plan, pn, p)
176 let l0: i64 = ces_field(plan, p, e, 0, off)
177 let t0: i64 = off[0]
178 let nf: i64 = ces_nfields(plan, p, e)
179 if ces_span_is(plan, t0, l0, CES_RUNG_TAG) == 1 { if nf >= CES_RUNG_NF {
180 let il: i64 = ces_field(plan, p, e, CES_RUNG_F_ID, off)
181 if ces_span_is(plan, off[0], il, rung) == 1 {
182 f[RGC_F_DECLARED] = 1
183 f[RGC_F_DECLS] = f[RGC_F_DECLS] + 1
184 let sl: i64 = ces_field(plan, p, e, RGC_RUNG_F_SYMBOL, off)
185 f[RGC_F_SYM_O] = off[0]
186 f[RGC_F_SYM_L] = sl
187 let cl: i64 = ces_field(plan, p, e, CES_RUNG_F_COST, off)
188 let c: i64 = ces_parse_deciu(plan, off[0], cl)
189 if c > 0 { f[RGC_F_COST] = c }
190 }
191 } }
192 if ces_span_is(plan, t0, l0, CES_LOG_TAG) == 1 { if nf >= CES_LOG_NF {
193 let gl: i64 = ces_field(plan, p, e, CES_LOG_F_RUNG, off)
194 if ces_span_is(plan, off[0], gl, rung) == 1 {
195 f[RGC_F_ROWS] = f[RGC_F_ROWS] + 1
196 let el: i64 = ces_field(plan, p, e, CES_LOG_F_EPOCH, off)
197 let epoch: i64 = ces_parse_int(plan, off[0], el)
198 let kl: i64 = ces_field(plan, p, e, CES_LOG_F_KIND, off)
199 let koff: i64 = off[0]
200 if epoch >= 0 {
201 if epoch > f[RGC_F_LAST] { f[RGC_F_LAST] = epoch }
202 if ces_span_is(plan, koff, kl, CES_LAND) == 1 { if epoch > f[RGC_F_LAND] { f[RGC_F_LAND] = epoch } }
203 if ces_span_is(plan, koff, kl, CES_RETRACT) == 1 { if epoch > f[RGC_F_RETRACT] { f[RGC_F_RETRACT] = epoch } }
204 if ces_span_is(plan, koff, kl, RGC_KIND_DONE) == 1 { if epoch > f[RGC_F_DONE] { f[RGC_F_DONE] = epoch } }
205 }
206 }
207 } }
208 p = e + 1
209 }
210 return f[RGC_F_DECLARED]
211}
212
213// a rung declares a contract when its symbol field is more than a dash
214func rgc_has_contract(plan: *u8, f: *i64) -> i64 {
215 if f[RGC_F_SYM_L] <= 0 { return 0 }
216 if f[RGC_F_SYM_L] == 1 { if plan[f[RGC_F_SYM_O]] == (CES_CH_DASH as u8) { return 0 } }
217 return 1
218}
219
220func rgc_span_eq2(a: *u8, aoff: i64, alen: i64, b: *u8, boff: i64, blen: i64) -> i64 {
221 if alen != blen { return 0 }
222 if alen < 0 { return 0 }
223 var i: i64 = 0
224 while i < alen { if a[aoff + i] != b[boff + i] { return 0 } i = i + 1 }
225 return 1
226}
227
228// the matrix row that names this contract: its symbol field is the symbol or _ABSENT_:<symbol>. o[0], o[1] receive the
229// organ span inside mx. Returns 1 found, 0 no row names it (the BUILT link is then UNOBSERVABLE, never FAILS).
230func rgc_matrix_organ(mx: *u8, mn: i64, plan: *u8, soff: i64, slen: i64, o: *i64) -> i64 {
231 let off: *i64 = sys_mmap(RGC_I64) as *i64
232 let pl: i64 = ces_slen(RGC_ABSENT_PFX)
233 var p: i64 = 0
234 while p < mn {
235 let e: i64 = ces_line_end(mx, mn, p)
236 if e > p { if mx[p] != (CES_CH_HASH as u8) { if ces_nfields(mx, p, e) >= RGC_MX_MIN_NF {
237 let yl: i64 = ces_field(mx, p, e, RGC_MX_F_SYMBOL, off)
238 let yoff: i64 = off[0]
239 var hit: i64 = rgc_span_eq2(mx, yoff, yl, plan, soff, slen)
240 if hit == 0 { if yl == pl + slen { if ces_span_is(mx, yoff, pl, RGC_ABSENT_PFX) == 1 { hit = rgc_span_eq2(mx, yoff + pl, slen, plan, soff, slen) } } }
241 if hit == 1 {
242 let ol: i64 = ces_field(mx, p, e, RGC_MX_F_ORGAN, off)
243 o[0] = off[0]
244 o[1] = ol
245 return 1
246 }
247 } } }
248 p = e + 1
249 }
250 return 0
251}
252
253// JOURNAL: any row of any kind names the rung
254func rgc_journal_link(rows: i64, why: *i64) -> i64 {
255 if rows > 0 { why[0] = RGC_W_ROWS; return RGC_HOLDS }
256 why[0] = RGC_W_NOROWS
257 return RGC_FAILS
258}
259
260// BUILT: no contract holds by declaration; a contract no matrix row names is UNOBSERVABLE; otherwise the symbol ruler
261// decides (1 present holds; 0 organ read and symbol absent, or -1 organ unreadable, both mean nobody built it)
262func rgc_built(has_contract: i64, resolved: i64, sd: i64, why: *i64) -> i64 {
263 if has_contract == 0 { why[0] = RGC_W_DECLARED; return RGC_HOLDS }
264 if resolved == 0 { why[0] = RGC_W_NOROW; return RGC_UNOBS }
265 if sd == 1 { why[0] = RGC_W_SYMBOL; return RGC_HOLDS }
266 why[0] = RGC_W_DARK
267 return RGC_FAILS
268}
269
270// LIVE: declaration first (the treat row's mcp field, verified PRESENT on the allowlist by the treatment judge), then
271// derivation (the contract organ's own tool on the allowlist). No contract and no treat tool is no subject at all.
272func rgc_live(has_contract: i64, treat_mcp: i64, allow_ok: i64, tool_on: i64, why: *i64) -> i64 {
273 if treat_mcp == 1 { why[0] = RGC_W_TREAT; return RGC_HOLDS }
274 if has_contract == 0 { why[0] = RGC_W_NOCONTRACT; return RGC_FAILS }
275 if allow_ok == 0 { why[0] = RGC_W_UNREADABLE; return RGC_UNOBS }
276 if tool_on == 1 { why[0] = RGC_W_TOOL; return RGC_HOLDS }
277 why[0] = RGC_W_UNREGISTERED
278 return RGC_FAILS
279}
280
281// TESTED: green_since is the newest GREEN roster row at or after the land over every journal the gate's evidence may live in
282// (rgc_roster_credit; CES_NONE when none). A GREEN row that was READ counts even if the gate has since left the roster conf -- the
283// STALE issue then caps the stage instead -- and even if another journal could not be read. jr_ok is 0 when a journal that could
284// hold the evidence could not be read: then the ABSENCE of a GREEN is UNOBSERVABLE, never a failure and never a pass.
285func rgc_tested(gate_resolved: i64, jr_ok: i64, green_since: i64, rostered: i64, why: *i64) -> i64 {
286 if gate_resolved == 0 { why[0] = RGC_W_NOGATE; return RGC_FAILS }
287 if green_since >= 0 { why[0] = RGC_W_GREEN; return RGC_HOLDS }
288 if jr_ok == 0 { why[0] = RGC_W_UNREADABLE; return RGC_UNOBS }
289 if rostered == 0 { why[0] = RGC_W_UNROSTERED; return RGC_FAILS }
290 why[0] = RGC_W_NOGREEN
291 return RGC_FAILS
292}
293
294// ---- the execution-surface census row (nx_execsurface): name TAB clock TAB cron TAB daemon TAB fork TAB roster TAB actlog
295// TAB first_surface. ONE classification of a row into what it proves about a run.
296const RGC_ES_F_CLOCK: i64 = 1
297const RGC_ES_F_CRON: i64 = 2
298const RGC_ES_F_DAEMON: i64 = 3
299const RGC_ES_F_FORK: i64 = 4
300const RGC_ES_F_ROSTER: i64 = 5
301const RGC_ES_F_ACTLOG: i64 = 6
302const RGC_ES_F_FIRST: i64 = 7
303const RGC_S_NONE: i64 = 0 // no surface, or only a reference (ref:)
304const RGC_S_MCP: i64 = 1 // only the MCP door (a seat's call)
305const RGC_S_BLIND: i64 = 2 // unattended, and no per-run witness this ruler reads: a plan step, a cron row, a daemon, a fork
306const RGC_S_ROSTER: i64 = 3 // the roster runs it: witnessed per run in the roster journal
307const RGC_S_CLOCK: i64 = 4 // a clock row runs it directly: witnessed per run in the clock's actlog rows
308const RGC_ES_OUT_CLASS: i64 = 0 // rgc_es_class out slots
309const RGC_ES_OUT_FO: i64 = 1
310const RGC_ES_OUT_FL: i64 = 2
311const RGC_ES_OUT_SLOTS: i64 = 3
312const RGC_SURF_CLOCK: *u8 = "clock"
313const RGC_SURF_PLAN: *u8 = "plan"
314
315// the tab field k of the line [p, e): length, offset in off[0]; CES_NONE when the line has no field k
316func rgc_tab_field(buf: *u8, p: i64, e: i64, k: i64, off: *i64) -> i64 {
317 var i: i64 = p
318 var fcount: i64 = 0
319 var s: i64 = p
320 while i < e {
321 if buf[i] == (RGC_CH_TAB as u8) {
322 if fcount == k { off[0] = s; return i - s }
323 fcount = fcount + 1
324 s = i + 1
325 }
326 i = i + 1
327 }
328 if fcount == k { off[0] = s; return e - s }
329 return CES_NONE
330}
331
332// the first byte of a one-byte-or-longer field is the digit 1
333func rgc_flag1(buf: *u8, p: i64, e: i64, k: i64, off: *i64) -> i64 {
334 let l: i64 = rgc_tab_field(buf, p, e, k, off)
335 if l == 1 { if buf[off[0]] == (CES_CH_0 + 1) as u8 { return 1 } }
336 return 0
337}
338
339// the census row named tool: returns 1 and fills out (class, first_surface span); 0 when the census has no such row
340func rgc_es_class(es: *u8, en: i64, tool: *u8, out: *i64) -> i64 {
341 let off: *i64 = sys_mmap(RGC_I64) as *i64
342 out[RGC_ES_OUT_CLASS] = RGC_S_NONE
343 out[RGC_ES_OUT_FO] = 0
344 out[RGC_ES_OUT_FL] = 0
345 var p: i64 = 0
346 while p < en {
347 let e: i64 = ces_line_end(es, en, p)
348 if e > p { if es[p] != (CES_CH_HASH as u8) {
349 let nl: i64 = rgc_tab_field(es, p, e, 0, off)
350 if ces_span_is(es, off[0], nl, tool) == 1 {
351 let fl: i64 = rgc_tab_field(es, p, e, RGC_ES_F_FIRST, off)
352 let fo: i64 = off[0]
353 if fl >= 0 { out[RGC_ES_OUT_FO] = fo; out[RGC_ES_OUT_FL] = fl }
354 var c: i64 = RGC_S_NONE
355 if rgc_flag1(es, p, e, RGC_ES_F_ACTLOG, off) == 1 { c = RGC_S_MCP }
356 var blind: i64 = rgc_flag1(es, p, e, RGC_ES_F_CLOCK, off) + rgc_flag1(es, p, e, RGC_ES_F_CRON, off) + rgc_flag1(es, p, e, RGC_ES_F_DAEMON, off)
357 let kl: i64 = rgc_tab_field(es, p, e, RGC_ES_F_FORK, off)
358 if kl > 0 { if kl != 1 { blind = blind + 1 } else { if es[off[0]] != (CES_CH_DASH as u8) { blind = blind + 1 } } }
359 if fl > 0 { if ces_slen(RGC_SURF_PLAN) <= fl { if ces_span_is(es, fo, ces_slen(RGC_SURF_PLAN), RGC_SURF_PLAN) == 1 { blind = blind + 1 } } }
360 if blind > 0 { c = RGC_S_BLIND }
361 if rgc_flag1(es, p, e, RGC_ES_F_ROSTER, off) == 1 { c = RGC_S_ROSTER }
362 if fl > 0 { if ces_span_is(es, fo, fl, RGC_SURF_CLOCK) == 1 { c = RGC_S_CLOCK } }
363 out[RGC_ES_OUT_CLASS] = c
364 return 1
365 }
366 } }
367 p = e + 1
368 }
369 return 0
370}
371
372// WIRED: declaration first (a COMPLETE treat row), then the surface census
373func rgc_wired(treat_complete: i64, es_ok: i64, es_found: i64, sclass: i64, why: *i64) -> i64 {
374 if treat_complete == 1 { why[0] = RGC_W_TREAT; return RGC_HOLDS }
375 if es_ok == 0 { why[0] = RGC_W_UNREADABLE; return RGC_UNOBS }
376 if es_found == 0 { why[0] = RGC_W_NOTINCENSUS; return RGC_UNOBS }
377 if sclass >= RGC_S_BLIND { why[0] = RGC_W_SURFACE; return RGC_HOLDS }
378 if sclass == RGC_S_MCP { why[0] = RGC_W_MCPONLY; return RGC_FAILS }
379 why[0] = RGC_W_NOSURFACE
380 return RGC_FAILS
381}
382
383// ---- the freshness margin: two census beats, the same margin the worklist allows before it is a fossil ----------------
384const RGC_WORK_BEAT_S: i64 = 86400 // the census beat (clock row rungclose): the list is rewritten once a day
385const RGC_WORK_BEATS_OF_MARGIN: i64 = 2 // one full missed beat of margin before a reading is called stale
386func rgc_margin_s() -> i64 { return RGC_WORK_BEAT_S * RGC_WORK_BEATS_OF_MARGIN }
387
388// OPERATED: run is the newest unattended run the evidence carries for the subject (CES_NONE when none). A run counts at
389// or after the land and inside the margin. A subject whose surface keeps no per-run witness is UNOBSERVABLE unless a treat
390// row names its agent (then its agent's runs are the witness and their absence is a failure).
391func rgc_operated(run: i64, land: i64, now: i64, act_ok: i64, sclass: i64, has_agent: i64, why: *i64) -> i64 {
392 if run >= 0 { if run >= land { if now - run <= rgc_margin_s() { why[0] = RGC_W_RUN; return RGC_HOLDS } } }
393 if act_ok == 0 { why[0] = RGC_W_UNREADABLE; return RGC_UNOBS }
394 if has_agent == 0 { if sclass == RGC_S_BLIND { why[0] = RGC_W_BLIND; return RGC_UNOBS } }
395 why[0] = RGC_W_NORUN
396 return RGC_FAILS
397}
398
399// PAGE: the page's mtime in seconds (below zero = it could not be read) against the newest land. A rung that never
400// journaled a land has nothing newer the page must carry: the page need only exist.
401func rgc_page(page_mtime: i64, land: i64, why: *i64) -> i64 {
402 if page_mtime < 0 { why[0] = RGC_W_UNREADABLE; return RGC_UNOBS }
403 if land < 0 { why[0] = RGC_W_PAGE; return RGC_HOLDS }
404 if page_mtime >= land { why[0] = RGC_W_PAGE; return RGC_HOLDS }
405 why[0] = RGC_W_PAGEOLD
406 return RGC_FAILS
407}
408
409// ---- the roster journal: epoch TAB roster TAB gate TAB VERDICT TAB exit= TAB ms= TAB lastline ------------------------------
410// Indexed ONCE per run (stride RGC_GX_STRIDE), production-roster rows only, so a thousand lookups never re-scan the journal.
411const RGC_GX_EPOCH: i64 = 0
412const RGC_GX_GOFF: i64 = 1
413const RGC_GX_GLEN: i64 = 2
414const RGC_GX_V: i64 = 3
415const RGC_GX_STRIDE: i64 = 4
416const RGC_GR_F_ROSTER: i64 = 1
417const RGC_GR_F_GATE: i64 = 2
418const RGC_GR_F_VERDICT: i64 = 3
419const RGC_ROSTER_PROD: *u8 = "roster" // trial runs write other roster names; only the production roster tests a rung
420const RGC_V_RED: i64 = 0
421const RGC_V_GREEN: i64 = 1
422const RGC_V_OTHER: i64 = 0 - 1 // SKIP, UNKNOWN: the gate abstained, which is neither
423const RGC_VERDICT_GREEN: *u8 = "GREEN"
424const RGC_VERDICT_RED: *u8 = "RED"
425// the gate facts (rgc_gate_facts)
426const RGC_G_ROWS: i64 = 0
427const RGC_G_NEWEST: i64 = 1 // the newest row's epoch
428const RGC_G_NEWEST_V: i64 = 2 // and its verdict
429const RGC_G_GREEN: i64 = 3 // the newest GREEN at or after `since` (CES_NONE when none)
430const RGC_G_FLIPS: i64 = 4 // GREEN/RED turns among rows at or after `since`
431const RGC_G_LAST_FLIP: i64 = 5
432const RGC_G_SLOTS: i64 = 6
433
434// the rows a buffer can hold: its line count plus one -- the bound an index is sized from, never a guess
435func rgc_lines(buf: *u8, n: i64) -> i64 {
436 var c: i64 = 1
437 var i: i64 = 0
438 while i < n { if buf[i] == (CES_CH_NL as u8) { c = c + 1 } i = i + 1 }
439 return c
440}
441
442func rgc_verdict_of(buf: *u8, off: i64, len: i64) -> i64 {
443 if ces_span_is(buf, off, len, RGC_VERDICT_GREEN) == 1 { return RGC_V_GREEN }
444 if ces_span_is(buf, off, len, RGC_VERDICT_RED) == 1 { return RGC_V_RED }
445 return RGC_V_OTHER
446}
447
448func rgc_gx_index(jr: *u8, jn: i64, ix: *i64, cap: i64) -> i64 {
449 let off: *i64 = sys_mmap(RGC_I64) as *i64
450 var rows: i64 = 0
451 var p: i64 = 0
452 while p < jn {
453 let e: i64 = ces_line_end(jr, jn, p)
454 if rows < cap {
455 let rl: i64 = rgc_tab_field(jr, p, e, RGC_GR_F_ROSTER, off)
456 if ces_span_is(jr, off[0], rl, RGC_ROSTER_PROD) == 1 {
457 let el: i64 = rgc_tab_field(jr, p, e, 0, off)
458 let ep: i64 = ces_parse_int(jr, off[0], el)
459 let gl: i64 = rgc_tab_field(jr, p, e, RGC_GR_F_GATE, off)
460 let go: i64 = off[0]
461 let vl: i64 = rgc_tab_field(jr, p, e, RGC_GR_F_VERDICT, off)
462 if ep >= 0 { if gl > 0 { if vl > 0 {
463 let b: i64 = rows * RGC_GX_STRIDE
464 ix[b + RGC_GX_EPOCH] = ep
465 ix[b + RGC_GX_GOFF] = go
466 ix[b + RGC_GX_GLEN] = gl
467 ix[b + RGC_GX_V] = rgc_verdict_of(jr, off[0], vl)
468 rows = rows + 1
469 } } }
470 }
471 }
472 p = e + 1
473 }
474 return rows
475}
476
477// every indexed row for one gate, in journal order. since below zero takes every row. Returns the gate's row count.
478func rgc_gate_facts(jr: *u8, ix: *i64, rows: i64, gate: *u8, since: i64, g: *i64) -> i64 {
479 g[RGC_G_ROWS] = 0
480 g[RGC_G_NEWEST] = CES_NONE
481 g[RGC_G_NEWEST_V] = RGC_V_OTHER
482 g[RGC_G_GREEN] = CES_NONE
483 g[RGC_G_FLIPS] = 0
484 g[RGC_G_LAST_FLIP] = CES_NONE
485 let gn: i64 = ces_slen(gate)
486 var prev: i64 = RGC_V_OTHER
487 var r: i64 = 0
488 while r < rows {
489 let b: i64 = r * RGC_GX_STRIDE
490 if ix[b + RGC_GX_GLEN] == gn { if ces_span_is(jr, ix[b + RGC_GX_GOFF], gn, gate) == 1 {
491 g[RGC_G_ROWS] = g[RGC_G_ROWS] + 1
492 let ep: i64 = ix[b + RGC_GX_EPOCH]
493 let v: i64 = ix[b + RGC_GX_V]
494 if ep >= g[RGC_G_NEWEST] { g[RGC_G_NEWEST] = ep; g[RGC_G_NEWEST_V] = v }
495 if ep >= since {
496 if v == RGC_V_GREEN { if ep > g[RGC_G_GREEN] { g[RGC_G_GREEN] = ep } }
497 if v != RGC_V_OTHER {
498 if prev != RGC_V_OTHER { if v != prev { g[RGC_G_FLIPS] = g[RGC_G_FLIPS] + 1; g[RGC_G_LAST_FLIP] = ep } }
499 prev = v
500 }
501 }
502 } }
503 r = r + 1
504 }
505 return g[RGC_G_ROWS]
506}
507
508// ---- THE ROSTER JOURNALS: a gate's roster evidence lives in the journal of every roster that runs it ------------------------
509// A per-pair journal table, RGC_RJ_STRIDE slots per roster pair in nx_rosterconf_lib's pair order: the journal's bytes and length
510// (RGC_RJ_UNREAD when it could not be read), its production-roster index and rows, and its ORIGIN -- the first pair naming the same
511// journal, whose slots carry the one read (the main and heavy beats share the production journal).
512const RGC_RJ_BUF: i64 = 0
513const RGC_RJ_LEN: i64 = 1
514const RGC_RJ_GX: i64 = 2
515const RGC_RJ_GXROWS: i64 = 3
516const RGC_RJ_ORIGIN: i64 = 4
517const RGC_RJ_STRIDE: i64 = 5
518const RGC_RJ_UNREAD: i64 = 0 - 1
519// what rgc_roster_credit hands back
520const RGC_RC_GREEN_IN: i64 = 0 // the pair whose journal supplied the newest GREEN since the land (CES_NONE when none)
521const RGC_RC_UNREAD: i64 = 1 // 1 when a journal that could hold the evidence could not be read
522const RGC_RC_CONSULTED: i64 = 2 // how many distinct journals were read or tried
523const RGC_RC_SLOTS: i64 = 3
524
525// the facts of one journal folded into the facts of all: rows and turns add, the newest row and the newest GREEN are the latest.
526// Turns are counted within each journal -- a gate's rows live in the journal of the roster that runs it, so a turn ACROSS two
527// journals (a gate moved between rosters) is not counted: the imprecision chosen, and named here
528func rgc_facts_merge(acc: *i64, one: *i64) -> i64 {
529 acc[RGC_G_ROWS] = acc[RGC_G_ROWS] + one[RGC_G_ROWS]
530 if one[RGC_G_NEWEST] > acc[RGC_G_NEWEST] { acc[RGC_G_NEWEST] = one[RGC_G_NEWEST]; acc[RGC_G_NEWEST_V] = one[RGC_G_NEWEST_V] }
531 if one[RGC_G_GREEN] > acc[RGC_G_GREEN] { acc[RGC_G_GREEN] = one[RGC_G_GREEN] }
532 acc[RGC_G_FLIPS] = acc[RGC_G_FLIPS] + one[RGC_G_FLIPS]
533 if one[RGC_G_LAST_FLIP] > acc[RGC_G_LAST_FLIP] { acc[RGC_G_LAST_FLIP] = one[RGC_G_LAST_FLIP] }
534 return acc[RGC_G_ROWS]
535}
536
537// THE ROSTER EVIDENCE OF ONE NAME: its facts (g) over the production journal -- pair 0's, ALWAYS read, so a GREEN earned there
538// counts even after the gate leaves its conf -- and over the journal of every pair whose conf lists the name (lists[k] = 1, decided
539// by the caller with the beat's own row grammar), each distinct journal read once (seen[] is np slots of scratch, gt RGC_G_SLOTS).
540// A journal that holds the name's rows while no pair listing the name writes it is NEVER consulted: evidence counts only where a
541// roster that runs the gate writes it. out receives the pair whose journal supplied the newest GREEN, whether a journal that could
542// hold evidence could not be read, and how many were consulted (RGC_RC_*). Returns the rows found.
543func rgc_roster_credit(jt: *i64, np: i64, lists: *i64, name: *u8, since: i64, g: *i64, gt: *i64, seen: *i64, out: *i64) -> i64 {
544 g[RGC_G_ROWS] = 0
545 g[RGC_G_NEWEST] = CES_NONE
546 g[RGC_G_NEWEST_V] = RGC_V_OTHER
547 g[RGC_G_GREEN] = CES_NONE
548 g[RGC_G_FLIPS] = 0
549 g[RGC_G_LAST_FLIP] = CES_NONE
550 out[RGC_RC_GREEN_IN] = CES_NONE
551 out[RGC_RC_UNREAD] = 0
552 out[RGC_RC_CONSULTED] = 0
553 var k: i64 = 0
554 while k < np { seen[k] = 0; k = k + 1 }
555 k = 0
556 while k < np {
557 var consult: i64 = lists[k]
558 if k == 0 { consult = 1 }
559 if consult == 1 {
560 let o: i64 = jt[k * RGC_RJ_STRIDE + RGC_RJ_ORIGIN]
561 if seen[o] == 0 {
562 seen[o] = 1
563 out[RGC_RC_CONSULTED] = out[RGC_RC_CONSULTED] + 1
564 let b: i64 = o * RGC_RJ_STRIDE
565 if jt[b + RGC_RJ_LEN] < 0 { out[RGC_RC_UNREAD] = 1 } else {
566 rgc_gate_facts(jt[b + RGC_RJ_BUF] as *u8, jt[b + RGC_RJ_GX] as *i64, jt[b + RGC_RJ_GXROWS], name, since, gt)
567 if gt[RGC_G_GREEN] > g[RGC_G_GREEN] { out[RGC_RC_GREEN_IN] = o }
568 rgc_facts_merge(g, gt)
569 }
570 }
571 }
572 k = k + 1
573 }
574 return g[RGC_G_ROWS]
575}
576
577// ---- the clock's own run journal: the actlog rows the clock writes for every job it fires ---------------------------------
578// epoch TAB clock TAB <the organ it forked> TAB run TAB ok|fail TAB clockjob lane=clock exit=<code>[ job=<clock row name>]
579// (nx_clock_sched clk_actlog). The organ column is the forked PATH only -- no arguments -- so a clock row that runs
580// `nx_plan_run.elf <plan>` is witnessed by a row naming nx_plan_run.elf and nothing else: the job= token the clock appends to
581// the last field is the only thing that says WHICH clock row ran. Indexed once per run, clock-lane rows whose process EXITED
582// (0 to 127): a code a shell reports as 128 plus a signal number is a death, not a verdict, and a negative code is a spawn
583// that never ran.
584const RGC_AX_EPOCH: i64 = 0
585const RGC_AX_COFF: i64 = 1 // the command column as written (the forked path; an older writer may carry arguments)
586const RGC_AX_CLEN: i64 = 2
587const RGC_AX_BOFF: i64 = 3 // its program: the first token's basename less .elf
588const RGC_AX_BLEN: i64 = 4
589const RGC_AX_JOFF: i64 = 5 // the clock row's name after job= in the last field; JLEN is CES_NONE when the row carries none
590const RGC_AX_JLEN: i64 = 6
591const RGC_AX_STRIDE: i64 = 7
592const RGC_JOB_KEY: *u8 = "job="
593const RGC_AL_F_LANE: i64 = 1
594const RGC_AL_F_CMD: i64 = 2
595const RGC_AL_F_NOTE: i64 = 5
596const RGC_LANE_CLOCK: *u8 = "clock"
597const RGC_EXIT_KEY: *u8 = "exit="
598const RGC_EXIT_SIGNALLED: i64 = 128 // UNIT: a shell reports death by signal N as 128 plus N
599const RGC_ELF_SFX: *u8 = ".elf"
600
601// the exit code after "exit=" in [p, e); CES_NONE when absent or unparsable (a leading minus is kept)
602func rgc_exit_code(buf: *u8, p: i64, e: i64) -> i64 {
603 let kl: i64 = ces_slen(RGC_EXIT_KEY)
604 var i: i64 = p
605 while i + kl <= e {
606 if ces_span_is(buf, i, kl, RGC_EXIT_KEY) == 1 {
607 var j: i64 = i + kl
608 var neg: i64 = 0
609 if j < e { if buf[j] == (RGC_CH_MINUS as u8) { neg = 1; j = j + 1 } }
610 var v: i64 = 0
611 var d: i64 = 0
612 var go: i64 = 1
613 while go == 1 {
614 if j >= e { go = 0 } else {
615 let c: i64 = buf[j] as i64
616 if c < CES_CH_0 { go = 0 } else { if c > CES_CH_9 { go = 0 } else { v = v * 10 + (c - CES_CH_0); d = d + 1; j = j + 1 } }
617 }
618 }
619 if d == 0 { return CES_NONE }
620 if neg == 1 { return 0 - v }
621 return v
622 }
623 i = i + 1
624 }
625 return CES_NONE
626}
627
628// THE CLOCK ROW A RUN WAS FIRED FOR: the value of the job= token inside [p, e), token-bounded on both sides -- the key opens the
629// field or follows a space (so subjob=x is not job=x) and the value runs to the next separator (so job=twincheckx is not
630// twincheck). Its length with its offset in o[0]; CES_NONE when the field carries no job.
631func rgc_job_span(buf: *u8, p: i64, e: i64, o: *i64) -> i64 {
632 let kl: i64 = ces_slen(RGC_JOB_KEY)
633 var i: i64 = p
634 while i + kl <= e {
635 var opens: i64 = 0
636 if i == p { opens = 1 } else { if buf[i - 1] == (RGC_CH_SP as u8) { opens = 1 } }
637 if opens == 1 { if ces_span_is(buf, i, kl, RGC_JOB_KEY) == 1 {
638 var t: i64 = i + kl
639 while t < e { let c: i64 = buf[t] as i64; if c == RGC_CH_SP { break } if c == RGC_CH_TAB { break } if c == RGC_CH_CR { break } t = t + 1 }
640 o[0] = i + kl
641 return t - (i + kl)
642 } }
643 i = i + 1
644 }
645 o[0] = CES_NONE
646 return CES_NONE
647}
648
649// the program span of a command span: its first token, after the last slash, less a trailing .elf. o[0] = offset.
650func rgc_prog_span(buf: *u8, coff: i64, clen: i64, o: *i64) -> i64 {
651 var t: i64 = coff
652 while t < coff + clen { if buf[t] == (RGC_CH_SP as u8) { break } t = t + 1 }
653 var s: i64 = coff
654 var k: i64 = coff
655 while k < t { if buf[k] == (RGC_CH_SLASH as u8) { s = k + 1 } k = k + 1 }
656 var l: i64 = t - s
657 let xl: i64 = ces_slen(RGC_ELF_SFX)
658 if l > xl { if ces_span_is(buf, t - xl, xl, RGC_ELF_SFX) == 1 { l = l - xl } }
659 o[0] = s
660 return l
661}
662
663func rgc_ax_index(al: *u8, an: i64, ix: *i64, cap: i64) -> i64 {
664 let off: *i64 = sys_mmap(RGC_I64) as *i64
665 let po: *i64 = sys_mmap(RGC_I64) as *i64
666 let jo: *i64 = sys_mmap(RGC_I64) as *i64
667 var rows: i64 = 0
668 var p: i64 = 0
669 while p < an {
670 let e: i64 = ces_line_end(al, an, p)
671 if rows < cap {
672 let ll: i64 = rgc_tab_field(al, p, e, RGC_AL_F_LANE, off)
673 if ces_span_is(al, off[0], ll, RGC_LANE_CLOCK) == 1 {
674 let el: i64 = rgc_tab_field(al, p, e, 0, off)
675 let ep: i64 = ces_parse_int(al, off[0], el)
676 let cl: i64 = rgc_tab_field(al, p, e, RGC_AL_F_CMD, off)
677 let co: i64 = off[0]
678 let nl: i64 = rgc_tab_field(al, p, e, RGC_AL_F_NOTE, off)
679 var code: i64 = CES_NONE
680 var jl: i64 = CES_NONE
681 jo[0] = CES_NONE
682 if nl > 0 {
683 code = rgc_exit_code(al, off[0], off[0] + nl)
684 jl = rgc_job_span(al, off[0], off[0] + nl, jo)
685 }
686 if ep >= 0 { if cl > 0 { if code >= 0 { if code < RGC_EXIT_SIGNALLED {
687 let b: i64 = rows * RGC_AX_STRIDE
688 ix[b + RGC_AX_EPOCH] = ep
689 ix[b + RGC_AX_COFF] = co
690 ix[b + RGC_AX_CLEN] = cl
691 ix[b + RGC_AX_BLEN] = rgc_prog_span(al, co, cl, po)
692 ix[b + RGC_AX_BOFF] = po[0]
693 ix[b + RGC_AX_JLEN] = jl
694 ix[b + RGC_AX_JOFF] = jo[0]
695 rows = rows + 1
696 } } } }
697 }
698 }
699 p = e + 1
700 }
701 return rows
702}
703
704// the newest indexed run whose PROGRAM is prog (a NUL-terminated name); CES_NONE when none
705func rgc_ax_newest_prog(al: *u8, ix: *i64, rows: i64, prog: *u8) -> i64 {
706 let pl: i64 = ces_slen(prog)
707 var best: i64 = CES_NONE
708 var r: i64 = 0
709 while r < rows {
710 let b: i64 = r * RGC_AX_STRIDE
711 if ix[b + RGC_AX_BLEN] == pl { if ces_span_is(al, ix[b + RGC_AX_BOFF], pl, prog) == 1 {
712 if ix[b + RGC_AX_EPOCH] > best { best = ix[b + RGC_AX_EPOCH] }
713 } }
714 r = r + 1
715 }
716 return best
717}
718
719// the newest indexed run whose WHOLE COMMAND equals the span [coff, coff+clen) of src; CES_NONE when none
720func rgc_ax_newest_cmd(al: *u8, ix: *i64, rows: i64, src: *u8, coff: i64, clen: i64) -> i64 {
721 var best: i64 = CES_NONE
722 var r: i64 = 0
723 while r < rows {
724 let b: i64 = r * RGC_AX_STRIDE
725 if rgc_span_eq2(al, ix[b + RGC_AX_COFF], ix[b + RGC_AX_CLEN], src, coff, clen) == 1 {
726 if ix[b + RGC_AX_EPOCH] > best { best = ix[b + RGC_AX_EPOCH] }
727 }
728 r = r + 1
729 }
730 return best
731}
732
733// the newest indexed run the clock fired FOR THE CLOCK ROW NAMED [noff, noff+nlen) of src -- its job= token, the one witness that
734// names the row whatever command it runs; CES_NONE when none
735func rgc_ax_newest_job(al: *u8, ix: *i64, rows: i64, src: *u8, noff: i64, nlen: i64) -> i64 {
736 var best: i64 = CES_NONE
737 if nlen <= 0 { return best }
738 var r: i64 = 0
739 while r < rows {
740 let b: i64 = r * RGC_AX_STRIDE
741 if ix[b + RGC_AX_JLEN] == nlen { if rgc_span_eq2(al, ix[b + RGC_AX_JOFF], nlen, src, noff, nlen) == 1 {
742 if ix[b + RGC_AX_EPOCH] > best { best = ix[b + RGC_AX_EPOCH] }
743 } }
744 r = r + 1
745 }
746 return best
747}
748
749// the epoch that opens a journal buffer (its first row's first field); CES_NONE when it does not open with digits
750func rgc_first_epoch(buf: *u8, n: i64) -> i64 {
751 var e: i64 = 0
752 while e < n { let c: i64 = buf[e] as i64; if c < CES_CH_0 { break } if c > CES_CH_9 { break } e = e + 1 }
753 if e == 0 { return CES_NONE }
754 return ces_parse_int(buf, 0, e)
755}
756
757// the clock plane's LAST row whose first field is name: the span of its LAST field (the organ command); CES_NONE when no row
758// names it. off[0] receives the offset. The LAST row, because the scheduler applies the last declaration of a name: a desired
759// plane keeps every put, so the first row of a re-declared job is a stale command (measured on searchship-dry, whose first row
760// still read nx_search_ship.elf dry). The live plane carries one row per name, so there the rule changes nothing.
761func rgc_clock_cmd(clock: *u8, cn: i64, noff: i64, nlen: i64, nsrc: *u8, off: *i64) -> i64 {
762 let fo: *i64 = sys_mmap(RGC_I64) as *i64
763 var best: i64 = CES_NONE
764 var p: i64 = 0
765 while p < cn {
766 let e: i64 = ces_line_end(clock, cn, p)
767 let l0: i64 = rgc_tab_field(clock, p, e, 0, fo)
768 if rgc_span_eq2(clock, fo[0], l0, nsrc, noff, nlen) == 1 {
769 var s: i64 = p
770 var i: i64 = p
771 while i < e { if clock[i] == (RGC_CH_TAB as u8) { s = i + 1 } i = i + 1 }
772 if s > p { off[0] = s; best = e - s }
773 }
774 p = e + 1
775 }
776 sys_munmap(fo as *u8, RGC_I64)
777 return best
778}
779
780// ---- ISSUES DEMOTE -----------------------------------------------------------------------------------------------------
781const RGC_I_RED: i64 = 0
782const RGC_I_RETRACT: i64 = 1
783const RGC_I_FLAKY: i64 = 2
784const RGC_I_STALE: i64 = 3
785const RGC_ISSUES: i64 = 4
786const RGC_FLAKY_TURNS: i64 = 2 // a verdict that went one way and came back: GREEN-RED-GREEN or RED-GREEN-RED
787
788// the issue flags of one rung (iss, RGC_ISSUES slots, each 0 or 1); returns how many are present
789func rgc_issues(g: *i64, land: i64, retract: i64, now: i64, iss: *i64) -> i64 {
790 var k: i64 = 0
791 while k < RGC_ISSUES { iss[k] = 0; k = k + 1 }
792 if g[RGC_G_ROWS] > 0 {
793 if g[RGC_G_NEWEST_V] == RGC_V_RED { iss[RGC_I_RED] = 1 }
794 if now - g[RGC_G_NEWEST] > rgc_margin_s() { iss[RGC_I_STALE] = 1 }
795 // two turns always set the last turn's epoch (the index keeps only rows with an epoch), so no NONE guard is needed here
796 if g[RGC_G_FLIPS] >= RGC_FLAKY_TURNS { if now - g[RGC_G_LAST_FLIP] <= rgc_margin_s() { iss[RGC_I_FLAKY] = 1 } }
797 }
798 if retract >= 0 { if retract > land { iss[RGC_I_RETRACT] = 1 } }
799 return iss[RGC_I_RED] + iss[RGC_I_RETRACT] + iss[RGC_I_FLAKY] + iss[RGC_I_STALE]
800}
801
802// the stage after the ceilings: RED and RETRACT cap at LIVE (the tested claim is withdrawn), FLAKY and STALE at ALPHA
803// (tested once, and not evidence enough for anything that others build on)
804func rgc_cap(state: i64, iss: *i64) -> i64 {
805 var s: i64 = state
806 if iss[RGC_I_RED] + iss[RGC_I_RETRACT] > 0 { if s > RGC_ST_LIVE { s = RGC_ST_LIVE } }
807 if iss[RGC_I_FLAKY] + iss[RGC_I_STALE] > 0 { if s > RGC_ST_ALPHA { s = RGC_ST_ALPHA } }
808 return s
809}
810
811// rework: the SUBJECT must change. STALE is the harness's defect (the roster stopped running the gate), never the rung's.
812func rgc_rework(iss: *i64) -> i64 {
813 if iss[RGC_I_RED] + iss[RGC_I_RETRACT] + iss[RGC_I_FLAKY] > 0 { return 1 }
814 return 0
815}
816
817func rgc_issue_name(k: i64) -> *u8 {
818 if k == RGC_I_RED { return "RED" as *u8 }
819 if k == RGC_I_RETRACT { return "RETRACT" as *u8 }
820 if k == RGC_I_FLAKY { return "FLAKY" as *u8 }
821 if k == RGC_I_STALE { return "STALE" as *u8 }
822 return "-" as *u8
823}
824
825func rgc_issue_need(k: i64) -> *u8 {
826 if k == RGC_I_RED { return "the gate's newest roster row is RED: the subject regressed or the gate caught a defect -- rework it until the roster reads GREEN" as *u8 }
827 if k == RGC_I_RETRACT { return "a retract row is newer than the rung's newest land: the published claim was withdrawn -- rework and land again" as *u8 }
828 if k == RGC_I_FLAKY { return "the gate's verdict turned twice or more since the land: a flipping gate is not evidence -- find the nondeterminism (fixture, clock, shared path)" as *u8 }
829 if k == RGC_I_STALE { return "the roster has not run the gate inside the freshness margin: the evidence is old -- check the roster beats and that the gate is still a row of a roster conf (every conf a clock beat runs is listed after this remedy)" as *u8 }
830 return "-" as *u8
831}
832
833// the present issues comma-joined at o, or a single dash when none; returns the new fill
834func rgc_issue_word(iss: *i64, dst: *u8, o0: i64) -> i64 {
835 var o: i64 = o0
836 var k: i64 = 0
837 var any: i64 = 0
838 while k < RGC_ISSUES {
839 if iss[k] == 1 {
840 if any == 1 { dst[o] = RGC_CH_COMMA as u8; o = o + 1 }
841 o = ces_cat(dst, o, rgc_issue_name(k))
842 any = 1
843 }
844 k = k + 1
845 }
846 if any == 0 { dst[o] = CES_CH_DASH as u8; o = o + 1 }
847 dst[o] = 0 as u8
848 return o
849}
850
851func rgc_issue_word_bound() -> i64 {
852 var n: i64 = 1
853 var k: i64 = 0
854 while k < RGC_ISSUES { n = n + ces_slen(rgc_issue_name(k)) + 1; k = k + 1 }
855 return n
856}
857
858// the first present issue whose ceiling actually lowered the stage (the one that NAMES the cap); CES_NONE when none did
859func rgc_capping_issue(state: i64, iss: *i64) -> i64 {
860 if state > RGC_ST_LIVE {
861 if iss[RGC_I_RED] == 1 { return RGC_I_RED }
862 if iss[RGC_I_RETRACT] == 1 { return RGC_I_RETRACT }
863 }
864 if state > RGC_ST_ALPHA {
865 if iss[RGC_I_FLAKY] == 1 { return RGC_I_FLAKY }
866 if iss[RGC_I_STALE] == 1 { return RGC_I_STALE }
867 }
868 return CES_NONE
869}
870
871// ---- the climb --------------------------------------------------------------------------------------------------------
872// state = OPEN plus the count of links that hold IN ORDER; stop[0] receives the first link that does not (RGC_LINKS when
873// every link holds). An undeclared rung is NORUNG whatever the links say.
874func rgc_state(declared: i64, links: *i64, stop: *i64) -> i64 {
875 stop[0] = 0
876 if declared == 0 { return RGC_ST_NORUNG }
877 var k: i64 = 0
878 var go: i64 = 1
879 while go == 1 { if k >= RGC_LINKS { go = 0 } else { if links[k] == RGC_HOLDS { k = k + 1 } else { go = 0 } } }
880 stop[0] = k
881 return RGC_ST_OPEN + k
882}
883
884func rgc_state_name(s: i64) -> *u8 {
885 if s == RGC_ST_NORUNG { return "NORUNG" as *u8 }
886 if s == RGC_ST_OPEN { return "OPEN" as *u8 }
887 if s == RGC_ST_STARTED { return "STARTED" as *u8 }
888 if s == RGC_ST_BUILT { return "BUILT" as *u8 }
889 if s == RGC_ST_LIVE { return "LIVE" as *u8 }
890 if s == RGC_ST_ALPHA { return "ALPHA" as *u8 }
891 if s == RGC_ST_BETA { return "BETA" as *u8 }
892 if s == RGC_ST_OPERATED { return "OPERATED" as *u8 }
893 if s == RGC_ST_PRODUCTION { return "PRODUCTION" as *u8 }
894 return "UNKNOWN" as *u8
895}
896
897func rgc_link_name(k: i64) -> *u8 {
898 if k == RGC_L_JOURNAL { return "journal" as *u8 }
899 if k == RGC_L_BUILT { return "built" as *u8 }
900 if k == RGC_L_LIVE { return "live" as *u8 }
901 if k == RGC_L_TESTED { return "tested" as *u8 }
902 if k == RGC_L_WIRED { return "wired" as *u8 }
903 if k == RGC_L_OPERATED { return "operated" as *u8 }
904 if k == RGC_L_PAGE { return "page" as *u8 }
905 return "-" as *u8
906}
907
908// the WEAKEST code: a link index (0..RGC_LINKS-1), RGC_LINKS when every link holds, or rgc_weak_issue0() plus an issue
909// index when an issue's ceiling capped the stage below what the links earned. DERIVED from the link and issue counts, so
910// a link added to the climb can never collide with an issue code.
911func rgc_weak_issue0() -> i64 { return RGC_LINKS + 1 }
912func rgc_weak_codes() -> i64 { return rgc_weak_issue0() + RGC_ISSUES }
913func rgc_weak_name(w: i64) -> *u8 {
914 if w < RGC_LINKS { return rgc_link_name(w) }
915 if w >= rgc_weak_issue0() { if w < rgc_weak_codes() {
916 let k: i64 = w - rgc_weak_issue0()
917 if k == RGC_I_RED { return "red" as *u8 }
918 if k == RGC_I_RETRACT { return "retract" as *u8 }
919 if k == RGC_I_FLAKY { return "flaky" as *u8 }
920 if k == RGC_I_STALE { return "stale" as *u8 }
921 } }
922 return "-" as *u8
923}
924
925func rgc_hold_name(v: i64) -> *u8 {
926 if v == RGC_HOLDS { return "HOLDS" as *u8 }
927 if v == RGC_FAILS { return "FAILS" as *u8 }
928 return "UNOBSERVABLE" as *u8
929}
930
931// ---- the close-first worklist (EC57 c) -------------------------------------------------------------------------------
932// One row per rung that is STARTED and not PRODUCTION, written by the fleet census and read by the seat's ready digest, so
933// a seat is shown the rungs NEAREST to done before any new work. The formatter and the reader both live HERE: two organs
934// that must agree on a wire agree by construction, never by discipline.
935// head: <hash> RUNGCLOSE-WORKLIST asof=<epoch> ... the list says when it was made, so a reader can refuse a fossil
936// row: work|<stage 2..7>|<domain>|<rung>|<weakest link or capping issue>|<unobservable 0 or 1>|<newest journal epoch>|<cost deci-u>
937const RGC_WORK_TAG: *u8 = "work"
938const RGC_WORK_HEAD: *u8 = " RUNGCLOSE-WORKLIST asof="
939const RGC_WORK_HEAD_TAIL: *u8 = " src=nx_rungclose-census-all fields=state,domain,rung,weakest,unobservable,last_epoch,cost_deciu states=2-STARTED,3-BUILT,4-LIVE,5-ALPHA,6-BETA,7-OPERATED\n"
940const RGC_WORK_NF: i64 = 8
941const RGC_WORK_F_STATE: i64 = 1
942const RGC_WORK_F_DOM: i64 = 2
943const RGC_WORK_F_RUNG: i64 = 3
944const RGC_WORK_F_WEAK: i64 = 4
945const RGC_WORK_F_UNOBS: i64 = 5
946const RGC_WORK_F_LAST: i64 = 6
947const RGC_WORK_F_COST: i64 = 7
948const RGC_WORK_FRESH: i64 = 1
949const RGC_WORK_STALE: i64 = 0
950const RGC_W_TOTAL: i64 = 0 // rgc_work_pick accounting: well-formed rows, rows on the asked board, rows picked, malformed rows
951const RGC_W_MATCHED: i64 = 1
952const RGC_W_SHOWN: i64 = 2
953const RGC_W_MALFORMED: i64 = 3
954const RGC_W_SLOTS: i64 = 4
955const RGC_PICK_STRIDE: i64 = 2 // a pick is a line span: start, end
956const RGC_CH_STAR: i64 = 42
957
958// a rung belongs on the list when somebody started it and it is not in production
959func rgc_work_member(state: i64) -> i64 {
960 if state <= RGC_ST_OPEN { return 0 }
961 if state >= RGC_ST_PRODUCTION { return 0 }
962 return 1
963}
964
965// the bytes a row needs beyond its domain and rung names: the tag, the pipes and the newline, three numbers, the flag
966// digit, the longest weakest-name and the terminator -- derived, so a writer sizes its buffer from its inputs
967func rgc_work_row_fixed() -> i64 {
968 var longest: i64 = 0
969 var k: i64 = 0
970 while k < rgc_weak_codes() {
971 let l: i64 = ces_slen(rgc_weak_name(k))
972 if l > longest { longest = l }
973 k = k + 1
974 }
975 return ces_slen(RGC_WORK_TAG) + RGC_WORK_NF + CES_NUM_CAP * 3 + longest + 2
976}
977
978func rgc_work_pipe(buf: *u8, o: i64) -> i64 {
979 buf[o] = CES_CH_PIPE as u8
980 return o + 1
981}
982
983// one row appended at o0; returns the new fill
984func rgc_work_row(buf: *u8, o0: i64, state: i64, dom: *u8, rung: *u8, weakest: i64, unobs: i64, last: i64, cost: i64) -> i64 {
985 var o: i64 = ces_cat(buf, o0, RGC_WORK_TAG)
986 o = rgc_work_pipe(buf, o)
987 o = ces_fmt_int(buf, o, state)
988 o = rgc_work_pipe(buf, o)
989 o = ces_cat(buf, o, dom)
990 o = rgc_work_pipe(buf, o)
991 o = ces_cat(buf, o, rung)
992 o = rgc_work_pipe(buf, o)
993 o = ces_cat(buf, o, rgc_weak_name(weakest))
994 o = rgc_work_pipe(buf, o)
995 o = ces_fmt_int(buf, o, unobs)
996 o = rgc_work_pipe(buf, o)
997 o = ces_fmt_int(buf, o, last)
998 o = rgc_work_pipe(buf, o)
999 o = ces_fmt_int(buf, o, cost)
1000 buf[o] = CES_CH_NL as u8
1001 o = o + 1
1002 buf[o] = 0 as u8
1003 return o
1004}
1005
1006func rgc_work_head_bound() -> i64 {
1007 return ces_slen(RGC_WORK_HEAD) + ces_slen(RGC_WORK_HEAD_TAIL) + CES_NUM_CAP + 2
1008}
1009
1010// the head line: the list's own generated-at, so freshness is read from the list and never from a file time
1011func rgc_work_head(buf: *u8, o0: i64, asof: i64) -> i64 {
1012 buf[o0] = CES_CH_HASH as u8
1013 var o: i64 = ces_cat(buf, o0 + 1, RGC_WORK_HEAD)
1014 o = ces_fmt_int(buf, o, asof)
1015 o = ces_cat(buf, o, RGC_WORK_HEAD_TAIL)
1016 return o
1017}
1018
1019// the generated-at epoch off the head line; CES_NONE when the head is absent or carries no number
1020func rgc_work_asof(buf: *u8, n: i64) -> i64 {
1021 let hl: i64 = ces_slen(RGC_WORK_HEAD)
1022 if n < hl + 1 { return CES_NONE }
1023 if buf[0] != (CES_CH_HASH as u8) { return CES_NONE }
1024 if ces_span_is(buf, 1, hl, RGC_WORK_HEAD) == 0 { return CES_NONE }
1025 var e: i64 = hl + 1
1026 var go: i64 = 1
1027 while go == 1 {
1028 if e >= n { go = 0 } else {
1029 let c: i64 = buf[e] as i64
1030 if c < CES_CH_0 { go = 0 } else { if c > CES_CH_9 { go = 0 } else { e = e + 1 } }
1031 }
1032 }
1033 return ces_parse_int(buf, hl + 1, e - (hl + 1))
1034}
1035
1036// FRESH only when the list says when it was made, that moment is not in the future, and it is inside the margin. An
1037// absent or future stamp is UNOBSERVABLE (CES_NONE): could-not-tell is never fresh and is never merely stale.
1038func rgc_work_fresh(asof: i64, now: i64) -> i64 {
1039 if asof < 0 { return CES_NONE }
1040 if asof > now { return CES_NONE }
1041 if now - asof > rgc_margin_s() { return RGC_WORK_STALE }
1042 return RGC_WORK_FRESH
1043}
1044
1045// the state of a well-formed row; 0 when the line is not a work row at all; CES_NONE when it carries the tag and is
1046// malformed (a wrong field count, or a state that is not a member state): counted by the picker, never half-read
1047func rgc_work_line_state(buf: *u8, p: i64, e: i64, off: *i64) -> i64 {
1048 if e <= p { return 0 }
1049 let l0: i64 = ces_field(buf, p, e, 0, off)
1050 if ces_span_is(buf, off[0], l0, RGC_WORK_TAG) == 0 { return 0 }
1051 if ces_nfields(buf, p, e) != RGC_WORK_NF { return CES_NONE }
1052 let sl: i64 = ces_field(buf, p, e, RGC_WORK_F_STATE, off)
1053 let s: i64 = ces_parse_int(buf, off[0], sl)
1054 if rgc_work_member(s) == 0 { return CES_NONE }
1055 return s
1056}
1057
1058func rgc_work_line_dom_is(buf: *u8, p: i64, e: i64, dom: *u8, off: *i64) -> i64 {
1059 let dl: i64 = ces_field(buf, p, e, RGC_WORK_F_DOM, off)
1060 return ces_span_is(buf, off[0], dl, dom)
1061}
1062
1063// touched inside the WIP window (the same seven days the WIP census calls active); an unknown epoch is not active
1064func rgc_work_line_active(buf: *u8, p: i64, e: i64, now: i64, off: *i64) -> i64 {
1065 let ll: i64 = ces_field(buf, p, e, RGC_WORK_F_LAST, off)
1066 let last: i64 = ces_parse_int(buf, off[0], ll)
1067 if last < 0 { return 0 }
1068 if now - last <= CES_WIP_ACTIVE_S { return 1 }
1069 return 0
1070}
1071
1072// THE CLOSE-FIRST ORDER: nearest to production first (OPERATED down to STARTED); inside a stage, rungs touched inside the
1073// WIP window before stale ones; inside that, the list's own order. dom empty or a star takes every board. picks receives
1074// up to maxn line spans (start, end); acc the accounting (RGC_W_*). Returns how many were picked.
1075func rgc_work_pick(buf: *u8, n: i64, dom: *u8, now: i64, maxn: i64, picks: *i64, acc: *i64) -> i64 {
1076 let off: *i64 = sys_mmap(RGC_I64) as *i64
1077 var k: i64 = 0
1078 while k < RGC_W_SLOTS { acc[k] = 0; k = k + 1 }
1079 var all: i64 = 0
1080 let dl: i64 = ces_slen(dom)
1081 if dl == 0 { all = 1 }
1082 if dl == 1 { if dom[0] == (RGC_CH_STAR as u8) { all = 1 } }
1083 var p: i64 = 0
1084 while p < n {
1085 let e: i64 = ces_line_end(buf, n, p)
1086 let s0: i64 = rgc_work_line_state(buf, p, e, off)
1087 if s0 == CES_NONE { acc[RGC_W_MALFORMED] = acc[RGC_W_MALFORMED] + 1 }
1088 if s0 > 0 {
1089 acc[RGC_W_TOTAL] = acc[RGC_W_TOTAL] + 1
1090 var m0: i64 = all
1091 if m0 == 0 { m0 = rgc_work_line_dom_is(buf, p, e, dom, off) }
1092 acc[RGC_W_MATCHED] = acc[RGC_W_MATCHED] + m0
1093 }
1094 p = e + 1
1095 }
1096 var picked: i64 = 0
1097 var state: i64 = RGC_ST_OPERATED
1098 while state >= RGC_ST_STARTED {
1099 var want_active: i64 = 1
1100 while want_active >= 0 {
1101 var q: i64 = 0
1102 while q < n {
1103 let e2: i64 = ces_line_end(buf, n, q)
1104 if picked < maxn { if rgc_work_line_state(buf, q, e2, off) == state {
1105 var m: i64 = all
1106 if m == 0 { m = rgc_work_line_dom_is(buf, q, e2, dom, off) }
1107 if m == 1 { if rgc_work_line_active(buf, q, e2, now, off) == want_active {
1108 picks[picked * RGC_PICK_STRIDE] = q
1109 picks[picked * RGC_PICK_STRIDE + 1] = e2
1110 picked = picked + 1
1111 } }
1112 } }
1113 q = e2 + 1
1114 }
1115 want_active = want_active - 1
1116 }
1117 state = state - 1
1118 }
1119 acc[RGC_W_SHOWN] = picked
1120 return picked
1121}
1122
1123// ---- THE STAGE SNAPSHOT (v2): every rung's stage, written WHOLE by the fleet census and read back by the NEXT census, so a
1124// change of stage is a MEASURED EVENT with a date -- the closer needs no seat, and a demotion is on the record.
1125// head: <hash> RUNGCLOSE-STAGES asof=<epoch> fields=domain,rung,state,issues,since,rework
1126// row: stage|<domain>|<rung>|<state 1..8>|<issues or a dash>|<since: the census that first read this stage and issues>|<rework>
1127const RGC_STAGE_TAG: *u8 = "stage"
1128const RGC_STAGE_HEAD: *u8 = " RUNGCLOSE-STAGES asof="
1129const RGC_STAGE_HEAD_TAIL: *u8 = " fields=domain,rung,state,issues,since,rework\n"
1130const RGC_STAGE_NF: i64 = 7
1131const RGC_SG_F_DOM: i64 = 1
1132const RGC_SG_F_RUNG: i64 = 2
1133const RGC_SG_F_STATE: i64 = 3
1134const RGC_SG_F_ISSUES: i64 = 4
1135const RGC_SG_F_SINCE: i64 = 5
1136const RGC_SG_F_REWORK: i64 = 6
1137const RGC_SG_OUT_STATE: i64 = 0 // rgc_stage_find out slots
1138const RGC_SG_OUT_SINCE: i64 = 1
1139const RGC_SG_OUT_IO: i64 = 2 // the issues span in the snapshot
1140const RGC_SG_OUT_IL: i64 = 3
1141const RGC_SG_OUT_SLOTS: i64 = 4
1142
1143func rgc_stage_row_fixed() -> i64 {
1144 return ces_slen(RGC_STAGE_TAG) + RGC_STAGE_NF + CES_NUM_CAP * 3 + rgc_issue_word_bound() + 2
1145}
1146
1147func rgc_stage_head_bound() -> i64 {
1148 return ces_slen(RGC_STAGE_HEAD) + ces_slen(RGC_STAGE_HEAD_TAIL) + CES_NUM_CAP + 2
1149}
1150
1151func rgc_stage_head(buf: *u8, o0: i64, asof: i64) -> i64 {
1152 buf[o0] = CES_CH_HASH as u8
1153 var o: i64 = ces_cat(buf, o0 + 1, RGC_STAGE_HEAD)
1154 o = ces_fmt_int(buf, o, asof)
1155 o = ces_cat(buf, o, RGC_STAGE_HEAD_TAIL)
1156 return o
1157}
1158
1159func rgc_stage_row(buf: *u8, o0: i64, dom: *u8, rung: *u8, state: i64, iss: *i64, since: i64, rework: i64) -> i64 {
1160 var o: i64 = ces_cat(buf, o0, RGC_STAGE_TAG)
1161 o = rgc_work_pipe(buf, o)
1162 o = ces_cat(buf, o, dom)
1163 o = rgc_work_pipe(buf, o)
1164 o = ces_cat(buf, o, rung)
1165 o = rgc_work_pipe(buf, o)
1166 o = ces_fmt_int(buf, o, state)
1167 o = rgc_work_pipe(buf, o)
1168 o = rgc_issue_word(iss, buf, o)
1169 o = rgc_work_pipe(buf, o)
1170 o = ces_fmt_int(buf, o, since)
1171 o = rgc_work_pipe(buf, o)
1172 o = ces_fmt_int(buf, o, rework)
1173 buf[o] = CES_CH_NL as u8
1174 o = o + 1
1175 buf[o] = 0 as u8
1176 return o
1177}
1178
1179// is the line [p, e) the stage row for dom and rung? 1 fills out; 0 otherwise (malformed rows never match)
1180func rgc_stage_line_is(buf: *u8, p: i64, e: i64, dom: *u8, rung: *u8, out: *i64, off: *i64) -> i64 {
1181 if e <= p { return 0 }
1182 let l0: i64 = ces_field(buf, p, e, 0, off)
1183 if ces_span_is(buf, off[0], l0, RGC_STAGE_TAG) == 0 { return 0 }
1184 if ces_nfields(buf, p, e) != RGC_STAGE_NF { return 0 }
1185 let dl: i64 = ces_field(buf, p, e, RGC_SG_F_DOM, off)
1186 if ces_span_is(buf, off[0], dl, dom) == 0 { return 0 }
1187 let rl: i64 = ces_field(buf, p, e, RGC_SG_F_RUNG, off)
1188 if ces_span_is(buf, off[0], rl, rung) == 0 { return 0 }
1189 let sl: i64 = ces_field(buf, p, e, RGC_SG_F_STATE, off)
1190 out[RGC_SG_OUT_STATE] = ces_parse_int(buf, off[0], sl)
1191 let tl: i64 = ces_field(buf, p, e, RGC_SG_F_SINCE, off)
1192 out[RGC_SG_OUT_SINCE] = ces_parse_int(buf, off[0], tl)
1193 let il: i64 = ces_field(buf, p, e, RGC_SG_F_ISSUES, off)
1194 out[RGC_SG_OUT_IO] = off[0]
1195 out[RGC_SG_OUT_IL] = il
1196 return 1
1197}
1198
1199// the previous census's row for dom and rung, searched from the cursor forward and then from the top: the census walks
1200// the boards in the same order every run, so the next row is almost always where the cursor stands. cur[0] advances past
1201// a hit. Returns 1 found (out filled), 0 when the snapshot has no such row.
1202func rgc_stage_find(buf: *u8, n: i64, dom: *u8, rung: *u8, cur: *i64, out: *i64) -> i64 {
1203 let off: *i64 = sys_mmap(RGC_I64) as *i64
1204 var start: i64 = cur[0]
1205 if start < 0 { start = 0 }
1206 if start > n { start = n }
1207 var pass: i64 = 0
1208 while pass < 2 {
1209 var p: i64 = 0
1210 var stop: i64 = n
1211 if pass == 0 { p = start } else { stop = start }
1212 while p < stop {
1213 let e: i64 = ces_line_end(buf, n, p)
1214 if rgc_stage_line_is(buf, p, e, dom, rung, out, off) == 1 { cur[0] = e + 1; return 1 }
1215 p = e + 1
1216 }
1217 pass = pass + 1
1218 }
1219 return 0
1220}
1221
1222// the ledger's transition word: FROM>TO, or NEW>TO for a rung the previous census did not carry
1223func rgc_move_word(dst: *u8, o0: i64, from: i64, to: i64) -> i64 {
1224 var o: i64 = o0
1225 if from < 0 { o = ces_cat(dst, o, "NEW" as *u8) } else { o = ces_cat(dst, o, rgc_state_name(from)) }
1226 dst[o] = RGC_CH_GT as u8
1227 o = o + 1
1228 o = ces_cat(dst, o, rgc_state_name(to))
1229 dst[o] = 0 as u8
1230 return o
1231}
1232
1233// ---- the board journal's row grammar (EC55 item 3): log|<epoch>|<rung>|<kind>|<text> ------------------------------------
1234// A journal row is written by GRAMMAR, never by hand: a stray pipe in a hand-built row invents a field, a line break
1235// invents a row, and a kind the boards do not render is a row nobody reads. One builder, one validator, here.
1236const RGC_KIND_MEASURE: *u8 = "measure"
1237const RGC_KIND_LESSON: *u8 = "lesson"
1238const RGC_KIND_QUEUE: *u8 = "queue"
1239const RGC_CH_CR: i64 = 13
1240const RGC_LOG_PIPES: i64 = 4 // log|epoch|rung|kind|text
1241
1242// a kind the boards render: measure, land, retract, lesson, queue, done
1243func rgc_kind_ok(kind: *u8) -> i64 {
1244 let n: i64 = ces_slen(kind)
1245 if ces_span_is(kind, 0, n, RGC_KIND_MEASURE) == 1 { return 1 }
1246 if ces_span_is(kind, 0, n, CES_LAND) == 1 { return 1 }
1247 if ces_span_is(kind, 0, n, CES_RETRACT) == 1 { return 1 }
1248 if ces_span_is(kind, 0, n, RGC_KIND_LESSON) == 1 { return 1 }
1249 if ces_span_is(kind, 0, n, RGC_KIND_QUEUE) == 1 { return 1 }
1250 if ces_span_is(kind, 0, n, RGC_KIND_DONE) == 1 { return 1 }
1251 return 0
1252}
1253
1254// the text must not break the row: 1 ok, 0 empty, CES_NONE when it carries a pipe or a line break (off[0] = where)
1255func rgc_text_ok(text: *u8, off: *i64) -> i64 {
1256 let n: i64 = ces_slen(text)
1257 if n == 0 { return 0 }
1258 var i: i64 = 0
1259 while i < n {
1260 let c: i64 = text[i] as i64
1261 var bad: i64 = 0
1262 if c == CES_CH_PIPE { bad = 1 }
1263 if c == CES_CH_NL { bad = 1 }
1264 if c == RGC_CH_CR { bad = 1 }
1265 if bad == 1 { off[0] = i; return CES_NONE }
1266 i = i + 1
1267 }
1268 return 1
1269}
1270
1271// room for one row: its three names, the epoch, the pipes, a leading and a trailing newline and the terminator
1272func rgc_log_row_bound(rung: *u8, kind: *u8, text: *u8) -> i64 {
1273 return ces_slen(CES_LOG_TAG) + ces_slen(rung) + ces_slen(kind) + ces_slen(text) + CES_NUM_CAP + RGC_LOG_PIPES + 3
1274}
1275
1276// the row, built. lead_nl = 1 puts a newline FIRST, for a file that does not end in one (the row must start a line).
1277func rgc_log_row(buf: *u8, o0: i64, lead_nl: i64, epoch: i64, rung: *u8, kind: *u8, text: *u8) -> i64 {
1278 var o: i64 = o0
1279 if lead_nl == 1 { buf[o] = CES_CH_NL as u8; o = o + 1 }
1280 o = ces_cat(buf, o, CES_LOG_TAG)
1281 o = rgc_work_pipe(buf, o)
1282 o = ces_fmt_int(buf, o, epoch)
1283 o = rgc_work_pipe(buf, o)
1284 o = ces_cat(buf, o, rung)
1285 o = rgc_work_pipe(buf, o)
1286 o = ces_cat(buf, o, kind)
1287 o = rgc_work_pipe(buf, o)
1288 o = ces_cat(buf, o, text)
1289 buf[o] = CES_CH_NL as u8
1290 o = o + 1
1291 buf[o] = 0 as u8
1292 return o
1293}