nx_rungclose.nx source
↩ module page · 1385 lines · 71331 B
1// nx_rungclose.nx -- CLI over nx_rungclose_lib (ecosystem EC57 v2): ONE MEASURED stage per rung, the weakest link BY NAME,
2// and the census that moves every rung with NO SEAT and NO SIGNATURE in the loop.
3// nx_rungclose check <domain> <rung> [anchor] every link with its evidence and the rule that decided it, the issues,
4// the stage, the weakest link and its remedy; verdict LAST
5// nx_rungclose census <domain> [anchor] the stage of every rung on the board, counted, the partition summing
6// nx_rungclose census all [anchor] every published board. Writes the fleet status line, the close-first
7// worklist and the STAGE SNAPSHOT whole (temp, then rename), and appends
8// one ledger row per rung whose stage moved since the last census --
9// THE CLOSER: the clock row rungclose runs it daily, nobody signs anything
10// nx_rungclose journal <domain> <rung> <kind> <text> [anchor] one board journal row, by grammar
11// nx_rungclose close|reopen <domain> <rung> <actor> [anchor] RETIRED 2026-09-18 (operator: completion needs no manual
12// sign-off): prints the check and writes NOTHING
13// exit: 0 PRODUCTION, CENSUSED or JOURNALED | 1 below PRODUCTION or REFUSED | 2 usage | 3 UNOBSERVABLE or NO-PLAN
14// Evidence, read once per run: the board's plan and matrix (both published trees), tool_allowlist.conf,
15// EVERY ROSTER as a (conf, journal) pair (nx_rosterconf_lib: the runner's default knowledge/gateroster.conf writing the production
16// journal RCF_JRNL, knowledge/status/gateroster.jrnl, then each pair an agent-plane row runs through nx_gate_roster_run beat -- the
17// heavy roster into the production journal, the slow rosters into their OWN journals, any later roster without an edit here):
18// every conf, the production journal always, and the journal of every pair whose conf lists the gate being judged -- so a slow
19// beat's GREEN is credited where that beat writes it, and printed as green_in=; the execution-surface census
20// knowledge/status/execsurface.tsv, the actlog's tail window (the freshness margin, read by a doubling ladder so its size is
21// the window's, never a guess), the clock DESIRED plane, the contract organ's source, the gate artifacts the ship loop's own
22// resolver finds (nx_organ_ship_lib: knowledge/organ_gate.conf, then derivation), and the published page's mtime.
23// A run given an [anchor] writes every artifact BESIDE that path, never production.
24// license_tier: ORIGINAL No hw writes (Rule 26).
25import "nx_syscalls.nx"
26import "nx_estate_path.nx"
27import "nx_barfresh_lib.nx"
28import "nx_store_seed_lib.nx"
29import "nx_treatment_lib.nx"
30import "nx_symdecl_lib.nx"
31import "nx_srcfresh.nx"
32import "nx_gate.nx"
33import "nx_logtail.nx"
34import "nx_organ_ship_lib.nx"
35import "nx_rosterconf_lib.nx"
36import "nx_rungclose_lib.nx"
37
38const RP_EXIT_DONE: i64 = 0
39const RP_EXIT_NOT: i64 = 1
40const RP_EXIT_USAGE: i64 = 2
41const RP_EXIT_UNOBS: i64 = 3
42const RP_PATH_CAP: i64 = 1024
43const RP_MODE_644: i64 = 420
44const RP_ALLOW: *u8 = "tool_allowlist.conf"
45// the production roster journal is RCF_JRNL (nx_rosterconf_lib), and every other roster journal is read from the pair naming it:
46// a roster the ruler counts is always a roster whose evidence the ruler reads where that roster writes it
47const RP_SURFACES: *u8 = "knowledge/status/execsurface.tsv"
48const RP_ACTLOG: *u8 = "knowledge/status/actlog.jrnl"
49const RP_GATE_CONF: *u8 = "knowledge/organ_gate.conf"
50const RP_BUILT_DIR: *u8 = "buildroot/_build/"
51const RP_BUILT_SFX: *u8 = ".sov.elf"
52const RP_LIVE_SFX: *u8 = ".elf"
53const RP_SRC_SFX: *u8 = ".nx"
54const RP_LIB_SFX: *u8 = "_lib"
55const RP_TAIL_FIRST_SHARE: i64 = 64 // the actlog window's first probe reads 1/64th of the journal and the ladder DOUBLES until
56 // the window's oldest row predates the margin: this sets how many probes a read costs,
57 // never what it can see (the ladder ends at the whole file)
58const RP_CLOCK_PLANE: *u8 = "knowledge/store/clockjobs-" // the DESIRED plane: the treat judge reads it, as nx_treatment does
59const RP_CLOCK_LIVE: *u8 = "knowledge/store/clocksched-" // the LIVE plane: what the scheduler actually runs
60const RP_PLANE_CAP: i64 = 1048576 // the clock DESIRED plane read, the same reserve nx_treatment declares; a fill makes agent reads UNPROVEN
61const RP_WF_PFX: *u8 = "knowledge/store/plan-"
62const RP_WF_SFX: *u8 = "-manifest.txt"
63const RP_ANCHOR_DEFAULT: *u8 = "knowledge/status/ws_sync.jrnl" // the default anchor: production artifacts keep their own names
64const RP_LEDGER: *u8 = "knowledge/status/rungclose.jrnl"
65const RP_LEDGER_SFX: *u8 = ".rungclose"
66const RP_STATUS: *u8 = "knowledge/status/rungclose.status" // ONE canonical fleet line, truncate-written by census all
67const RP_STATUS_SFX: *u8 = ".rungclose.status"
68const RP_WORKLIST: *u8 = "knowledge/status/rungclose.worklist" // the close-first worklist, rewritten WHOLE by census all
69const RP_WORKLIST_SFX: *u8 = ".rungclose.worklist"
70const RP_STAGES: *u8 = "knowledge/status/rungclose.stages" // v2: every rung's stage, rewritten WHOLE; the next census diffs against it
71const RP_STAGES_SFX: *u8 = ".rungclose.stages"
72const RP_TMP_SFX: *u8 = ".tmp"
73const RP_PAGE_PFX: *u8 = "sites/nishifamily/compare/"
74const RP_PAGE_SFX: *u8 = "/index.html"
75const RP_MATRIX_SFX: *u8 = ".matrix"
76const RP_SRC_PFX: *u8 = "buildroot/"
77const RP_CENSUS_ACTOR: *u8 = "census"
78// the evidence, read once (rp_gather_shared + rp_gather_board)
79const RP_E_PLAN_P: i64 = 0
80const RP_E_PLAN_N: i64 = 1
81const RP_E_MX_P: i64 = 2
82const RP_E_MX_N: i64 = 3
83const RP_E_ALLOW_P: i64 = 4
84const RP_E_ALLOW_N: i64 = 5
85const RP_E_ROSTER_P: i64 = 6
86const RP_E_ROSTER_N: i64 = 7
87const RP_E_CLOCK_P: i64 = 8
88const RP_E_CLOCK_N: i64 = 9
89const RP_E_GR_P: i64 = 10 // the roster journal, whole
90const RP_E_GR_N: i64 = 11
91const RP_E_PAGE_S: i64 = 12
92const RP_E_TAB_P: i64 = 13
93const RP_E_TAB_ROWS: i64 = 14
94const RP_E_GX_P: i64 = 15 // its index
95const RP_E_GX_ROWS: i64 = 16
96const RP_E_ES_P: i64 = 17 // the surface census
97const RP_E_ES_N: i64 = 18
98const RP_E_AL_P: i64 = 19 // the actlog window
99const RP_E_AL_N: i64 = 20
100const RP_E_AX_P: i64 = 21 // its clock-lane index
101const RP_E_AX_ROWS: i64 = 22
102const RP_E_AL_BYTES: i64 = 23 // the whole journal's size
103const RP_E_AL_COVER: i64 = 24 // 1 when the window reaches back past the margin (or holds the whole file)
104const RP_E_NOW: i64 = 25
105const RP_E_ROOT_A: i64 = 26 // the resolver's roots and declaration file
106const RP_E_ROOT_B: i64 = 27
107const RP_E_GCONF: i64 = 28
108const RP_E_ES_ASOF: i64 = 29
109const RP_E_DOM: i64 = 30 // *u8 the board's domain name (the first half of a rung's gate key)
110const RP_E_RT_P: i64 = 31 // *i64 each roster pair's conf text, RCF_TEXT_STRIDE slots each, in pair order (pair 0 the default)
111const RP_E_RT_K: i64 = 32 // how many roster pairs
112const RP_E_RL_P: i64 = 33 // *u8 the roster pair list, conf TAB journal per line
113const RP_E_RL_N: i64 = 34
114const RP_E_RT_READ: i64 = 35 // how many distinct roster confs could be read
115const RP_E_R_BEATS: i64 = 36 // how many agent-plane rows are roster beats, whatever journal they write
116const RP_E_R_OTHER: i64 = 37 // of them, the beats writing a journal other than the production one (read from that journal)
117const RP_E_AGP_P: i64 = 38 // *u8 THE AGENT PLANE: what the scheduler runs (the live plane), else the desired plane
118const RP_E_AGP_N: i64 = 39
119const RP_E_AGP_SRC: i64 = 40 // RP_AGP_LIVE or RP_AGP_DESIRED: which one answered, printed as the evidence
120const RP_E_RJ_P: i64 = 41 // *i64 the roster journal table, RGC_RJ_STRIDE slots per roster pair (pair 0 = production)
121const RP_E_RJ_K: i64 = 42 // how many distinct roster journals
122const RP_E_RJ_READ: i64 = 43 // how many of them could be read
123const RP_E_RC_SEEN: i64 = 44 // *i64 rgc_roster_credit scratch, one slot per pair, allocated once for every rung
124const RP_E_RC_LISTS: i64 = 45 // *i64 which pairs' confs list the name being judged
125const RP_E_RC_GT: i64 = 46 // *i64 one journal's gate facts
126const RP_E_RT_DISTINCT: i64 = 47 // how many distinct roster confs
127const RP_E_SLOTS: i64 = 48
128const RP_AGP_LIVE: i64 = 1
129const RP_AGP_DESIRED: i64 = 2
130// A RUNG'S OWN GATE KEY in organ_gate.conf: "<domain>:<rung>". No organ name carries a colon, so the ship loop, whose targets are
131// organ names, can never match a row keyed this way; only the close ruler reads it.
132const RP_RUNG_KEY_SEP: *u8 = ":"
133// how the check line names a gate the RUNG declared, distinct from the resolver's own reasons, so the output says which of the
134// two declarations decided (a rung row and an organ row naming the same gate would otherwise print identically)
135const RP_GW_RUNG: i64 = 0 - 2
136const RP_GW_RUNG_NAME: *u8 = "organ_gate.conf-rung-declaration"
137// one evaluation: scalars, then scratch the caller allocates ONCE (rp_detail_new), so a census of two thousand rungs maps
138// its buffers once instead of once per rung
139const RP_D_SD: i64 = 0
140const RP_D_RESOLVED: i64 = 1
141const RP_D_TREAT_ROW: i64 = 2
142const RP_D_TREAT_V: i64 = 3
143const RP_D_MCP: i64 = 4
144const RP_D_ORGAN_O: i64 = 5
145const RP_D_ORGAN_L: i64 = 6
146const RP_D_TOOL_L: i64 = 7
147const RP_D_GATE_L: i64 = 8 // 0 when no gate resolves
148const RP_D_GATE_WHY: i64 = 9
149const RP_D_ROSTERED: i64 = 10
150const RP_D_SCLASS: i64 = 11
151const RP_D_ES_FOUND: i64 = 12
152const RP_D_RUN: i64 = 13
153const RP_D_RUN_SRC: i64 = 14
154const RP_D_AGENT: i64 = 15
155const RP_D_CHAIN: i64 = 16
156const RP_D_STAGE: i64 = 17
157const RP_D_REWORK: i64 = 18
158const RP_D_WEAK: i64 = 19
159const RP_D_NISS: i64 = 20
160const RP_D_WHY: i64 = 21 // *i64 per-link why codes
161const RP_D_GF: i64 = 22 // *i64 the gate facts
162const RP_D_GF2: i64 = 23 // *i64 the roster facts of the subject ITSELF (a gate the roster runs)
163const RP_D_ISS: i64 = 24 // *i64 issue flags
164const RP_D_TOOL: i64 = 25 // *u8 the subject tool
165const RP_D_GATE: i64 = 26 // *u8 the gate
166const RP_D_TARGET: i64 = 27 // *u8 the name the gate is resolved for
167const RP_D_DST: i64 = 28 // *u8 the resolver's artifact path
168const RP_D_TRIED: i64 = 29 // *u8 the resolver's search transcript
169const RP_D_W: i64 = 30 // *i64 one why
170const RP_D_STOP: i64 = 31
171const RP_D_O2: i64 = 32
172const RP_D_ESOUT: i64 = 33
173const RP_D_OFF: i64 = 34
174const RP_D_GWHY: i64 = 35
175const RP_D_SYM: i64 = 36 // *u8
176const RP_D_FULL: i64 = 37 // *u8
177const RP_D_RULE: i64 = 38 // *i64
178const RP_D_ES_FO: i64 = 39
179const RP_D_ES_FL: i64 = 40
180const RP_D_ROSTER_IN: i64 = 41 // which roster conf holds the gate (its list index), RCF_NONE when none does
181const RP_D_GREEN_IN: i64 = 42 // which roster pair's journal supplied the GREEN that TESTED holds on, RCF_NONE when none
182const RP_D_RCOUT: i64 = 43 // *i64 rgc_roster_credit's out (RGC_RC_SLOTS)
183const RP_D_SLOTS: i64 = 44
184const RP_RUN_PROGRAM: i64 = 1 // where the newest run was read: the clock ran the program itself,
185const RP_RUN_AGENT: i64 = 2 // the clock ran the treat row's agent command,
186const RP_RUN_ROSTER: i64 = 3 // or the roster ran the subject
187const RP_RUN_AGENT_JOB: i64 = 4 // the clock ran the treat row's agent, witnessed by its own job= name
188// what one board's census hands back
189const RP_W_BUF: i64 = 0
190const RP_W_FILL: i64 = 1
191const RP_W_ROWS: i64 = 2
192const RP_W_SBUF: i64 = 3 // its stage rows
193const RP_W_SFILL: i64 = 4
194const RP_W_MBUF: i64 = 5 // its ledger rows (moves)
195const RP_W_MFILL: i64 = 6
196const RP_W_SLOTS: i64 = 8
197// the fleet totals, after the stage counts
198const RP_T_RUNGS: i64 = 0
199const RP_T_UNOBS: i64 = 1
200const RP_T_DUPS: i64 = 2
201const RP_T_REWORK: i64 = 3
202const RP_T_PROMOTED: i64 = 4
203const RP_T_DEMOTED: i64 = 5
204const RP_T_ENTERED: i64 = 6 // rungs that entered PRODUCTION this census
205const RP_T_NEW: i64 = 7
206const RP_T_ISS0: i64 = 8 // then one count per issue class
207const RP_T_EXTRA: i64 = 12
208// the previous snapshot handed to each board's census
209const RP_P_BUF: i64 = 0
210const RP_P_N: i64 = 1
211const RP_P_CUR: i64 = 2 // *i64 the cursor
212const RP_P_BASELINE: i64 = 3 // 1 when there is no previous snapshot: this census records, and moves nothing
213const RP_P_SLOTS: i64 = 4
214
215func rp_span_copy(buf: *u8, off: i64, len: i64) -> *u8 {
216 var l: i64 = len
217 if l < 0 { l = 0 }
218 let s: *u8 = sys_mmap(l + 1)
219 var i: i64 = 0
220 while i < l { s[i] = buf[off + i]; i = i + 1 }
221 s[l] = 0 as u8
222 return s
223}
224
225// a span into a buffer of cap bytes, NUL-terminated; -1 (and nothing copied) when it does not fit
226func rp_span_into(dst: *u8, cap: i64, buf: *u8, off: i64, len: i64) -> i64 {
227 dst[0] = 0 as u8
228 if len < 0 { return 0 - 1 }
229 if len >= cap { return 0 - 1 }
230 var i: i64 = 0
231 while i < len { dst[i] = buf[off + i]; i = i + 1 }
232 dst[len] = 0 as u8
233 return len
234}
235
236// a whole file resolved through the estate path probe; 0 bytes when absent (outp[0] = 0), so absent and empty differ by
237// the pointer
238func rp_read_estate(rel: *u8, outp: *i64) -> i64 {
239 let path: *u8 = sys_mmap(RP_PATH_CAP)
240 if ep_artifact_path(path, rel) == 0 { outp[0] = 0; return 0 }
241 let np: *i64 = sys_mmap(RGC_I64) as *i64
242 np[0] = 0
243 let b: *u8 = sys_read_file(path, np)
244 outp[0] = b as i64
245 return np[0]
246}
247
248// the board's matrix from the published trees, first then second
249func rp_read_matrix(dom: *u8, outp: *i64) -> i64 {
250 let path: *u8 = sys_mmap(CT_PATH_CAP)
251 let np: *i64 = sys_mmap(RGC_I64) as *i64
252 np[0] = 0
253 ct_build_path(ct_first_published(), dom, RP_MATRIX_SFX, path)
254 var b: *u8 = sys_read_file(path, np)
255 if np[0] <= 0 {
256 ct_build_path(ct_second_published(), dom, RP_MATRIX_SFX, path)
257 b = sys_read_file(path, np)
258 }
259 outp[0] = b as i64
260 return np[0]
261}
262
263// THE WINDOW OF AN APPEND-ONLY JOURNAL SINCE AN EPOCH, sized by the window and never by a guess: the tail doubles until its
264// oldest row predates `since` or the whole file is held (nx_logtail's honest tail reader does every read). env receives
265// the reader's envelope; cover[0] = 1 when the window reaches `since` or holds the whole file. Returns the bytes held, or
266// -1 when the journal cannot be opened (outp[0] = 0).
267func rp_read_since(rel: *u8, since: i64, outp: *i64, env: *i64, cover: *i64) -> i64 {
268 outp[0] = 0
269 cover[0] = 0
270 let path: *u8 = sys_mmap(RP_PATH_CAP)
271 if ep_artifact_path(path, rel) == 0 { return 0 - 1 }
272 let one: *u8 = sys_mmap(RGC_I64)
273 if lt_read_tail(path, one, 0, env) < 0 { return 0 - 1 }
274 let sz: i64 = env[0]
275 if sz <= 0 { outp[0] = one as i64; cover[0] = 1; return 0 }
276 var cap: i64 = sz / RP_TAIL_FIRST_SHARE
277 if cap <= 0 { cap = sz }
278 var n: i64 = 0
279 var buf: *u8 = one
280 var held: i64 = 0
281 var go: i64 = 1
282 while go == 1 {
283 if held > 0 { sys_munmap(buf, held + 1) }
284 buf = sys_mmap(cap + 1)
285 held = cap
286 n = lt_read_tail(path, buf, cap, env)
287 if n < 0 { return 0 - 1 }
288 if env[3] == 0 { cover[0] = 1; go = 0 } else {
289 let e0: i64 = rgc_first_epoch(buf, n)
290 if e0 >= 0 { if e0 <= since { cover[0] = 1; go = 0 } }
291 if go == 1 { if cap >= sz { go = 0 } else { cap = cap * 2; if cap > sz { cap = sz } } }
292 }
293 }
294 outp[0] = buf as i64
295 return n
296}
297
298// the estate root the resolver probes under: the allowlist's resolved path less its own name ("" when CWD is the root)
299func rp_root(out: *u8) -> i64 {
300 out[0] = 0 as u8
301 let p: *u8 = sys_mmap(RP_PATH_CAP)
302 if ep_artifact_path(p, RP_ALLOW) == 0 { return 0 }
303 var k: i64 = ces_slen(p) - ces_slen(RP_ALLOW)
304 if k < 0 { k = 0 }
305 var i: i64 = 0
306 while i < k { out[i] = p[i]; i = i + 1 }
307 out[k] = 0 as u8
308 return k
309}
310
311// EVERY ROSTER, read once per run through rp_read_estate, the one reader. The (conf, journal) pairs are derived from the agent plane
312// by nx_rosterconf_lib (the runner's default pair first, then each pair a beat row runs, whatever journal it writes); each conf's
313// text is read once under the first pair naming it and kept in pair order, and each journal likewise -- the production journal is
314// the read rp_gather_shared already made. Pair 0's conf is the default, so the treat judge keeps reading exactly the conf it read
315// before (ev ROSTER_P/N): the treat row is judged against the same evidence nx_treatment reads. Returns how many confs were read.
316func rp_gather_rosters(clock: *u8, cn: i64, ev: *i64) -> i64 {
317 let meta: *i64 = sys_mmap(RCF_META_SLOTS * RGC_I64) as *i64
318 let list: *u8 = sys_mmap(rcf_confs_bound(clock, cn))
319 let k: i64 = rcf_confs(clock, cn, list, meta)
320 let ln: i64 = meta[RCF_META_BYTES]
321 let texts: *i64 = sys_mmap(k * RCF_TEXT_STRIDE * RGC_I64) as *i64
322 let jt: *i64 = sys_mmap(k * RGC_RJ_STRIDE * RGC_I64) as *i64
323 let lo: *i64 = sys_mmap(RGC_I64) as *i64
324 let tp: *i64 = sys_mmap(RGC_I64) as *i64
325 let path: *u8 = sys_mmap(RP_PATH_CAP)
326 var nread: i64 = 0
327 var ndistinct: i64 = 0
328 var jread: i64 = 0
329 var jdistinct: i64 = 0
330 var i: i64 = 0
331 while i < k {
332 // the conf: read once, under the first pair that names it
333 let b: i64 = i * RCF_TEXT_STRIDE
334 texts[b + RCF_TEXT_P] = 0
335 texts[b + RCF_TEXT_N] = 0
336 let co: i64 = rcf_pair_origin(list, ln, i, RCF_PART_CONF)
337 if co < i {
338 texts[b + RCF_TEXT_P] = texts[co * RCF_TEXT_STRIDE + RCF_TEXT_P]
339 texts[b + RCF_TEXT_N] = texts[co * RCF_TEXT_STRIDE + RCF_TEXT_N]
340 } else {
341 ndistinct = ndistinct + 1
342 let cl: i64 = rcf_pair_conf(list, ln, i, lo)
343 if rp_span_into(path, RP_PATH_CAP, list, lo[0], cl) > 0 {
344 tp[0] = 0
345 let tn: i64 = rp_read_estate(path, tp)
346 texts[b + RCF_TEXT_P] = tp[0]
347 texts[b + RCF_TEXT_N] = tn
348 if tp[0] != 0 { nread = nread + 1 }
349 }
350 }
351 // the journal: read and indexed once, under the first pair that names it; the production journal is the read
352 // rp_gather_shared already made, so the largest file is never read twice
353 let jb: i64 = i * RGC_RJ_STRIDE
354 let jo: i64 = rcf_pair_origin(list, ln, i, RCF_PART_JRNL)
355 jt[jb + RGC_RJ_ORIGIN] = jo
356 jt[jb + RGC_RJ_LEN] = RGC_RJ_UNREAD
357 jt[jb + RGC_RJ_BUF] = 0
358 jt[jb + RGC_RJ_GX] = 0
359 jt[jb + RGC_RJ_GXROWS] = 0
360 if jo == i {
361 jdistinct = jdistinct + 1
362 let jl: i64 = rcf_pair_jrnl(list, ln, i, lo)
363 if ces_span_is(list, lo[0], jl, RCF_JRNL) == 1 {
364 if ev[RP_E_GR_N] >= 0 {
365 jt[jb + RGC_RJ_BUF] = ev[RP_E_GR_P]
366 jt[jb + RGC_RJ_LEN] = ev[RP_E_GR_N]
367 jt[jb + RGC_RJ_GX] = ev[RP_E_GX_P]
368 jt[jb + RGC_RJ_GXROWS] = ev[RP_E_GX_ROWS]
369 jread = jread + 1
370 }
371 } else {
372 if rp_span_into(path, RP_PATH_CAP, list, lo[0], jl) > 0 {
373 tp[0] = 0
374 let jn: i64 = rp_read_estate(path, tp)
375 if tp[0] != 0 {
376 jt[jb + RGC_RJ_BUF] = tp[0]
377 jt[jb + RGC_RJ_LEN] = jn
378 if jn > 0 {
379 let gcap: i64 = rgc_lines(tp[0] as *u8, jn)
380 let gx: *i64 = sys_mmap(gcap * RGC_GX_STRIDE * RGC_I64) as *i64
381 jt[jb + RGC_RJ_GX] = gx as i64
382 jt[jb + RGC_RJ_GXROWS] = rgc_gx_index(tp[0] as *u8, jn, gx, gcap)
383 }
384 jread = jread + 1
385 }
386 }
387 }
388 }
389 i = i + 1
390 }
391 ev[RP_E_ROSTER_P] = texts[RCF_TEXT_P]
392 ev[RP_E_ROSTER_N] = texts[RCF_TEXT_N]
393 ev[RP_E_RT_P] = texts as i64
394 ev[RP_E_RT_K] = k
395 ev[RP_E_RT_DISTINCT] = ndistinct
396 ev[RP_E_RL_P] = list as i64
397 ev[RP_E_RL_N] = ln
398 ev[RP_E_RT_READ] = nread
399 ev[RP_E_R_BEATS] = meta[RCF_META_BEATS]
400 ev[RP_E_R_OTHER] = meta[RCF_META_OTHER]
401 ev[RP_E_RJ_P] = jt as i64
402 ev[RP_E_RJ_K] = jdistinct
403 ev[RP_E_RJ_READ] = jread
404 ev[RP_E_RC_SEEN] = sys_mmap(k * RGC_I64) as i64
405 ev[RP_E_RC_LISTS] = sys_mmap(k * RGC_I64) as i64
406 ev[RP_E_RC_GT] = sys_mmap(RGC_G_SLOTS * RGC_I64) as i64
407 return nread
408}
409
410// THE ROSTER EVIDENCE OF ONE NAME (a gate, or a subject a roster runs): which pairs' confs list it, read with the beat's own row
411// grammar, then rgc_roster_credit over the production journal and those pairs' journals. g receives the facts, out the rest.
412func rp_roster_evidence(ev: *i64, name: *u8, nl: i64, since: i64, g: *i64, out: *i64) -> i64 {
413 let k: i64 = ev[RP_E_RT_K]
414 let texts: *i64 = ev[RP_E_RT_P] as *i64
415 let lists: *i64 = ev[RP_E_RC_LISTS] as *i64
416 var i: i64 = 0
417 while i < k {
418 lists[i] = rcf_has_gate(texts[i * RCF_TEXT_STRIDE + RCF_TEXT_P] as *u8, texts[i * RCF_TEXT_STRIDE + RCF_TEXT_N], name, nl)
419 i = i + 1
420 }
421 return rgc_roster_credit(ev[RP_E_RJ_P] as *i64, k, lists, name, since, g, ev[RP_E_RC_GT] as *i64, ev[RP_E_RC_SEEN] as *i64, out)
422}
423
424// the roster confs as one comma-joined word, after the label: what a STALE or unrostered reader is told to check
425func rp_roster_confs_tail(ev: *i64) -> i64 {
426 let n: i64 = ev[RP_E_RL_N]
427 let w: *u8 = sys_mmap(n + 1)
428 rcf_join_confs(ev[RP_E_RL_P] as *u8, n, w, 0)
429 gw(" roster_confs=" as *u8); gw(w)
430 sys_munmap(w, n + 1)
431 return 0
432}
433
434// the evidence every board shares, read ONCE per run
435func rp_gather_shared(ev: *i64) -> i64 {
436 let now: i64 = sys_now_realtime_sec()
437 ev[RP_E_NOW] = now
438 let ap: *i64 = sys_mmap(RGC_I64) as *i64
439 ev[RP_E_ALLOW_N] = rp_read_estate(RP_ALLOW, ap)
440 ev[RP_E_ALLOW_P] = ap[0]
441 let clock: *u8 = sys_mmap(RP_PLANE_CAP)
442 var cn: i64 = sts_load(RP_CLOCK_PLANE, clock, RP_PLANE_CAP)
443 if cn >= RP_PLANE_CAP - 1 { cn = 0 }
444 if cn < 0 { cn = 0 }
445 ev[RP_E_CLOCK_P] = clock as i64
446 ev[RP_E_CLOCK_N] = cn
447 // THE AGENT PLANE: what the scheduler RUNS is the live plane; when it cannot be read (absent, empty, or filled past its
448 // reserve) the desired plane stands in, read last-declaration-wins as the scheduler reads it (rgc_clock_cmd). Agents resolve
449 // and rosters derive from it, so neither can name a command or a roster the scheduler is not running.
450 let live: *u8 = sys_mmap(RP_PLANE_CAP)
451 var ln: i64 = sts_load(RP_CLOCK_LIVE, live, RP_PLANE_CAP)
452 if ln >= RP_PLANE_CAP - 1 { ln = 0 }
453 if ln < 0 { ln = 0 }
454 if ln > 0 {
455 ev[RP_E_AGP_P] = live as i64
456 ev[RP_E_AGP_N] = ln
457 ev[RP_E_AGP_SRC] = RP_AGP_LIVE
458 } else {
459 sys_munmap(live, RP_PLANE_CAP)
460 ev[RP_E_AGP_P] = clock as i64
461 ev[RP_E_AGP_N] = cn
462 ev[RP_E_AGP_SRC] = RP_AGP_DESIRED
463 }
464 // the production roster journal, whole, and its production-roster index sized from its own line count
465 let gp: *i64 = sys_mmap(RGC_I64) as *i64
466 let grn: i64 = rp_read_estate(RCF_JRNL, gp)
467 ev[RP_E_GR_P] = gp[0]
468 ev[RP_E_GR_N] = grn
469 if gp[0] == 0 { ev[RP_E_GR_N] = 0 - 1 }
470 ev[RP_E_GX_ROWS] = 0
471 if grn > 0 {
472 let gcap: i64 = rgc_lines(gp[0] as *u8, grn)
473 let gx: *i64 = sys_mmap(gcap * RGC_GX_STRIDE * RGC_I64) as *i64
474 ev[RP_E_GX_P] = gx as i64
475 ev[RP_E_GX_ROWS] = rgc_gx_index(gp[0] as *u8, grn, gx, gcap)
476 }
477 // the rosters are derived from the agent plane and share the production journal just read, so they are gathered after both
478 rp_gather_rosters(ev[RP_E_AGP_P] as *u8, ev[RP_E_AGP_N], ev)
479 // the surface census, whole
480 let sp: *i64 = sys_mmap(RGC_I64) as *i64
481 let sn: i64 = rp_read_estate(RP_SURFACES, sp)
482 ev[RP_E_ES_P] = sp[0]
483 ev[RP_E_ES_N] = sn
484 if sp[0] == 0 { ev[RP_E_ES_N] = 0 - 1 }
485 // the actlog window since the margin, and its clock-lane index
486 let lp: *i64 = sys_mmap(RGC_I64) as *i64
487 let env: *i64 = sys_mmap(LT_ENV_SLOTS * RGC_I64) as *i64
488 let cov: *i64 = sys_mmap(RGC_I64) as *i64
489 let an: i64 = rp_read_since(RP_ACTLOG, now - rgc_margin_s(), lp, env, cov)
490 ev[RP_E_AL_P] = lp[0]
491 ev[RP_E_AL_N] = an
492 ev[RP_E_AL_BYTES] = env[0]
493 ev[RP_E_AL_COVER] = cov[0]
494 ev[RP_E_AX_ROWS] = 0
495 if an > 0 {
496 let acap: i64 = rgc_lines(lp[0] as *u8, an)
497 let ax: *i64 = sys_mmap(acap * RGC_AX_STRIDE * RGC_I64) as *i64
498 ev[RP_E_AX_P] = ax as i64
499 ev[RP_E_AX_ROWS] = rgc_ax_index(lp[0] as *u8, an, ax, acap)
500 }
501 // the resolver's roots: the promoted serving root, then the build scratch; its declaration file
502 let ra: *u8 = sys_mmap(RP_PATH_CAP)
503 let rl: i64 = rp_root(ra)
504 let rb: *u8 = sys_mmap(RP_PATH_CAP)
505 var bo: i64 = ces_cat(rb, 0, ra)
506 bo = ces_cat(rb, bo, RP_BUILT_DIR)
507 let gc: *u8 = sys_mmap(RP_PATH_CAP)
508 var go: i64 = ces_cat(gc, 0, ra)
509 go = ces_cat(gc, go, RP_GATE_CONF)
510 ev[RP_E_ROOT_A] = ra as i64
511 ev[RP_E_ROOT_B] = rb as i64
512 ev[RP_E_GCONF] = gc as i64
513 return rl
514}
515
516// the evidence of ONE board. Returns 1 when the plan was readable.
517func rp_gather_board(dom: *u8, ev: *i64) -> i64 {
518 let lenp: *i64 = sys_mmap(RGC_I64) as *i64
519 let which: *i64 = sys_mmap(RGC_I64) as *i64
520 lenp[0] = 0
521 ev[RP_E_DOM] = dom as i64
522 let plan: *u8 = bf_read_plan(ct_first_published(), ct_second_published(), dom, lenp, which)
523 ev[RP_E_PLAN_P] = plan as i64
524 ev[RP_E_PLAN_N] = lenp[0]
525 if lenp[0] <= 0 { return 0 }
526 let mp: *i64 = sys_mmap(RGC_I64) as *i64
527 ev[RP_E_MX_N] = rp_read_matrix(dom, mp)
528 ev[RP_E_MX_P] = mp[0]
529 let pg: *u8 = sys_mmap(RP_PATH_CAP)
530 var po: i64 = ces_cat(pg, 0, RP_PAGE_PFX)
531 po = ces_cat(pg, po, dom)
532 po = ces_cat(pg, po, RP_PAGE_SFX)
533 let pgr: *u8 = sys_mmap(RP_PATH_CAP)
534 var pms: i64 = 0 - 1
535 if ep_artifact_path(pgr, pg) == 1 {
536 let ns: i64 = sf_mtime_ns(pgr)
537 if ns >= 0 { pms = ns / SF_NS_PER_SEC }
538 }
539 ev[RP_E_PAGE_S] = pms
540 let cap: i64 = trt_cap(lenp[0])
541 let tab: *i64 = sys_mmap(cap * TRT_STRIDE * RGC_I64) as *i64
542 ev[RP_E_TAB_ROWS] = trt_scan(plan, lenp[0], tab, cap)
543 ev[RP_E_TAB_P] = tab as i64
544 return 1
545}
546
547func rp_detail_new() -> *i64 {
548 let d: *i64 = sys_mmap(RP_D_SLOTS * RGC_I64) as *i64
549 d[RP_D_WHY] = sys_mmap(RGC_LINKS * RGC_I64) as i64
550 d[RP_D_GF] = sys_mmap(RGC_G_SLOTS * RGC_I64) as i64
551 d[RP_D_GF2] = sys_mmap(RGC_G_SLOTS * RGC_I64) as i64
552 d[RP_D_ISS] = sys_mmap(RGC_ISSUES * RGC_I64) as i64
553 d[RP_D_TOOL] = sys_mmap(OSL_NAMECAP) as i64
554 d[RP_D_GATE] = sys_mmap(OSL_NAMECAP) as i64
555 d[RP_D_TARGET] = sys_mmap(OSL_NAMECAP) as i64
556 d[RP_D_DST] = sys_mmap(OSL_PATHCAP) as i64
557 d[RP_D_TRIED] = sys_mmap(osl_tried_cap()) as i64
558 d[RP_D_W] = sys_mmap(RGC_I64) as i64
559 d[RP_D_STOP] = sys_mmap(RGC_I64) as i64
560 d[RP_D_O2] = sys_mmap(2 * RGC_I64) as i64
561 d[RP_D_ESOUT] = sys_mmap(RGC_ES_OUT_SLOTS * RGC_I64) as i64
562 d[RP_D_OFF] = sys_mmap(RGC_I64) as i64
563 d[RP_D_GWHY] = sys_mmap(RGC_I64) as i64
564 d[RP_D_SYM] = sys_mmap(OSL_NAMECAP) as i64
565 d[RP_D_FULL] = sys_mmap(RP_PATH_CAP) as i64
566 d[RP_D_RULE] = sys_mmap(RGC_I64) as i64
567 d[RP_D_RCOUT] = sys_mmap(RGC_RC_SLOTS * RGC_I64) as i64
568 return d
569}
570
571// the basename of the span [off, off+len) less a trailing suffix, into dst (cap bytes); its length, -1 when it does not fit
572func rp_base_into(dst: *u8, cap: i64, buf: *u8, off: i64, len: i64, sfx: *u8) -> i64 {
573 var s: i64 = off
574 var k: i64 = off
575 while k < off + len { if buf[k] == (RGC_CH_SLASH as u8) { s = k + 1 } k = k + 1 }
576 var l: i64 = off + len - s
577 let xl: i64 = ces_slen(sfx)
578 if l > xl { if ces_span_is(buf, off + len - xl, xl, sfx) == 1 { l = l - xl } }
579 return rp_span_into(dst, cap, buf, s, l)
580}
581
582// a NUL-terminated name ending in suffix sfx loses it IN PLACE; returns 1 when it did
583func rp_strip_sfx(name: *u8, sfx: *u8) -> i64 {
584 let n: i64 = ces_slen(name)
585 let xl: i64 = ces_slen(sfx)
586 if n <= xl { return 0 }
587 if ces_span_is(name, n - xl, xl, sfx) == 0 { return 0 }
588 name[n - xl] = 0 as u8
589 return 1
590}
591
592// the gate's name off the resolver's artifact path: after the last slash, up to the first dot
593func rp_gate_name(dst: *u8, path: *u8) -> i64 {
594 let n: i64 = ces_slen(path)
595 var s: i64 = 0
596 var k: i64 = 0
597 while k < n { if path[k] == (RGC_CH_SLASH as u8) { s = k + 1 } k = k + 1 }
598 var e: i64 = s
599 while e < n { if path[e] == (RGC_CH_DOT as u8) { break } e = e + 1 }
600 return rp_span_into(dst, OSL_NAMECAP, path, s, e - s)
601}
602
603// THE LINKS OF ONE RUNG from the gathered evidence; f receives the journal facts, d the detail. Returns declared.
604func rp_eval(rung: *u8, ev: *i64, links: *i64, f: *i64, d: *i64) -> i64 {
605 let plan: *u8 = ev[RP_E_PLAN_P] as *u8
606 let pn: i64 = ev[RP_E_PLAN_N]
607 let mx: *u8 = ev[RP_E_MX_P] as *u8
608 let why: *i64 = d[RP_D_WHY] as *i64
609 let w: *i64 = d[RP_D_W] as *i64
610 let g: *i64 = d[RP_D_GF] as *i64
611 let g2: *i64 = d[RP_D_GF2] as *i64
612 let iss: *i64 = d[RP_D_ISS] as *i64
613 let tool: *u8 = d[RP_D_TOOL] as *u8
614 let gate: *u8 = d[RP_D_GATE] as *u8
615 let target: *u8 = d[RP_D_TARGET] as *u8
616 let off: *i64 = d[RP_D_OFF] as *i64
617 let now: i64 = ev[RP_E_NOW]
618 var k: i64 = 0
619 while k < RP_D_WHY { d[k] = 0; k = k + 1 }
620 d[RP_D_TREAT_ROW] = 0 - 1
621 d[RP_D_TREAT_V] = 0 - 1
622 d[RP_D_SD] = 0 - 1
623 d[RP_D_RUN] = CES_NONE
624 d[RP_D_ES_FO] = 0
625 d[RP_D_ES_FL] = 0
626 tool[0] = 0 as u8
627 gate[0] = 0 as u8
628 target[0] = 0 as u8
629 k = 0
630 while k < RGC_G_SLOTS { g[k] = 0; g2[k] = 0; k = k + 1 }
631 g[RGC_G_NEWEST] = CES_NONE
632 g[RGC_G_GREEN] = CES_NONE
633 g2[RGC_G_NEWEST] = CES_NONE
634 let declared: i64 = rgc_journal(plan, pn, rung, f)
635 links[RGC_L_JOURNAL] = rgc_journal_link(f[RGC_F_ROWS], w)
636 why[RGC_L_JOURNAL] = w[0]
637 // BUILT: the contract symbol through the ONE symbol ruler, its organ named by the board's own matrix row
638 let hasc: i64 = rgc_has_contract(plan, f)
639 var resolved: i64 = 0
640 var sd: i64 = 0 - 1
641 if hasc == 1 {
642 let o: *i64 = d[RP_D_O2] as *i64
643 resolved = rgc_matrix_organ(mx, ev[RP_E_MX_N], plan, f[RGC_F_SYM_O], f[RGC_F_SYM_L], o)
644 if resolved == 1 {
645 d[RP_D_ORGAN_O] = o[0]
646 d[RP_D_ORGAN_L] = o[1]
647 let sym: *u8 = d[RP_D_SYM] as *u8
648 let full: *u8 = d[RP_D_FULL] as *u8
649 if rp_span_into(sym, OSL_NAMECAP, plan, f[RGC_F_SYM_O], f[RGC_F_SYM_L]) >= 0 {
650 var fo: i64 = ces_cat(full, 0, RP_SRC_PFX)
651 if o[1] + fo < RP_PATH_CAP { rp_span_into((((full as i64) + fo) as *u8), RP_PATH_CAP - fo, mx, o[0], o[1]) }
652 let rule: *i64 = d[RP_D_RULE] as *i64
653 sd = sd_present(full, sym, rule)
654 if sd < 0 { sd = sd_present(((full as i64) + fo) as *u8, sym, rule) }
655 }
656 }
657 }
658 d[RP_D_SD] = sd
659 d[RP_D_RESOLVED] = resolved
660 links[RGC_L_BUILT] = rgc_built(hasc, resolved, sd, w)
661 why[RGC_L_BUILT] = w[0]
662 // the treat row, judged against the same evidence nx_treatment reads
663 let tab: *i64 = ev[RP_E_TAB_P] as *i64
664 let r: i64 = trt_find(tab, ev[RP_E_TAB_ROWS], plan, rung)
665 d[RP_D_TREAT_ROW] = r
666 var treat_mcp: i64 = 0
667 var treat_complete: i64 = 0
668 var agent_o: i64 = 0
669 var agent_l: i64 = 0
670 if r >= 0 {
671 let b: i64 = r * TRT_STRIDE
672 var prim_ok: i64 = 0 - 1
673 var wf_ok: i64 = 0 - 1
674 if tab[b + TRT_ST_PRIM] == TRT_F_DECLARED {
675 let spp: *u8 = sys_mmap(RP_PATH_CAP)
676 prim_ok = ep_src_path(spp, rp_span_copy(plan, tab[b + TRT_PRIM_O], tab[b + TRT_PRIM_L]))
677 }
678 if tab[b + TRT_ST_WF] == TRT_F_DECLARED {
679 let wfp: *u8 = sys_mmap(RP_PATH_CAP)
680 var wo: i64 = ces_cat(wfp, 0, RP_WF_PFX)
681 wo = ces_cat(wfp, wo, rp_span_copy(plan, tab[b + TRT_WF_O], tab[b + TRT_WF_L]))
682 wo = ces_cat(wfp, wo, RP_WF_SFX)
683 let probe: *u8 = sys_mmap(RP_PATH_CAP)
684 wf_ok = ep_artifact_path(probe, wfp)
685 }
686 let v: i64 = trt_judge_row(tab, r, plan, ev[RP_E_ALLOW_P] as *u8, ev[RP_E_ALLOW_N], ev[RP_E_ROSTER_P] as *u8, ev[RP_E_ROSTER_N], ev[RP_E_CLOCK_P] as *u8, ev[RP_E_CLOCK_N], prim_ok, wf_ok)
687 d[RP_D_TREAT_V] = v
688 d[RP_D_MCP] = tab[b + TRT_ST_MCP]
689 if tab[b + TRT_ST_MCP] == TRT_F_PRESENT {
690 treat_mcp = 1
691 d[RP_D_TOOL_L] = rp_span_into(tool, OSL_NAMECAP, plan, tab[b + TRT_MCP_O], tab[b + TRT_MCP_L])
692 }
693 if v == TRT_V_COMPLETE { treat_complete = 1 }
694 if tab[b + TRT_ST_AGENT] == TRT_F_PRESENT { agent_o = tab[b + TRT_AGENT_O]; agent_l = tab[b + TRT_AGENT_L] }
695 }
696 // LIVE: the subject tool. Declared by the treat row, else derived from the contract organ: its basename, and for a lib
697 // the basename less _lib (the CLI the estate registers beside a lib), whichever the allowlist carries
698 var tool_on: i64 = 0
699 if treat_mcp == 0 { if resolved == 1 {
700 let tl: i64 = rp_base_into(tool, OSL_NAMECAP, mx, d[RP_D_ORGAN_O], d[RP_D_ORGAN_L], RP_SRC_SFX)
701 d[RP_D_TOOL_L] = tl
702 if tl > 0 {
703 if trt_allow_has(ev[RP_E_ALLOW_P] as *u8, ev[RP_E_ALLOW_N], tool, 0, tl) == 1 { tool_on = 1 } else {
704 if rp_strip_sfx(tool, RP_LIB_SFX) == 1 {
705 d[RP_D_TOOL_L] = ces_slen(tool)
706 if trt_allow_has(ev[RP_E_ALLOW_P] as *u8, ev[RP_E_ALLOW_N], tool, 0, d[RP_D_TOOL_L]) == 1 { tool_on = 1 }
707 }
708 }
709 }
710 } }
711 var allow_ok: i64 = 0
712 if ev[RP_E_ALLOW_N] > 0 { allow_ok = 1 }
713 links[RGC_L_LIVE] = rgc_live(hasc, treat_mcp, allow_ok, tool_on, w)
714 why[RGC_L_LIVE] = w[0]
715 // TESTED: the gate the ship loop's own resolver finds for the ORGAN (a lib resolves through its one-segment strip), or
716 // for the treat tool when the rung names no organ.
717 // A RUNG MAY NAME ITS OWN GATE, AND THAT DECLARATION IS TRIED FIRST: organ_gate.conf keyed "<domain>:<rung>". A library
718 // carries many rungs behind one organ, and one organ row can name only one gate -- the face library's row names the lip
719 // ratio gate (declared for the AT4 lip axis), so WITHOUT a rung key every other rung in that library (the E-line, the
720 // palpebral fissure) resolves its TESTED link to the LIP gate: a wrong proof, which that conf's own comment calls worse than
721 // none because it looks like it worked. Only a DECLARATION counts for the rung key (the gate it names is probed as an
722 // explicit gate), so no derivation rule can ever invent a rung's proof; a rung with no row resolves exactly as before.
723 var gate_ok: i64 = 0
724 let dst: *u8 = d[RP_D_DST] as *u8
725 let gw1: *i64 = d[RP_D_GWHY] as *i64
726 if ev[RP_E_DOM] != 0 {
727 var rkl: i64 = ces_cat(target, 0, ev[RP_E_DOM] as *u8)
728 rkl = ces_cat(target, rkl, RP_RUNG_KEY_SEP)
729 rkl = ces_cat(target, rkl, rung)
730 if osl_conf_gate(ev[RP_E_GCONF] as *u8, target, gate) == 1 {
731 if osl_gate_resolve(target, gate, ev[RP_E_GCONF] as *u8, ev[RP_E_ROOT_A] as *u8, RP_LIVE_SFX, ev[RP_E_ROOT_B] as *u8, RP_BUILT_SFX, d[RP_D_TRIED] as *u8, osl_tried_cap(), dst, gw1) == 1 {
732 let rgl: i64 = rp_gate_name(gate, dst)
733 if rgl > 0 { gate_ok = 1; d[RP_D_GATE_L] = rgl; d[RP_D_GATE_WHY] = RP_GW_RUNG }
734 }
735 }
736 }
737 var tgl: i64 = 0 - 1
738 if gate_ok == 0 {
739 if resolved == 1 { tgl = rp_base_into(target, OSL_NAMECAP, mx, d[RP_D_ORGAN_O], d[RP_D_ORGAN_L], RP_SRC_SFX) }
740 if tgl <= 0 { if d[RP_D_TOOL_L] > 0 { tgl = rp_span_into(target, OSL_NAMECAP, tool, 0, d[RP_D_TOOL_L]) } }
741 }
742 if tgl > 0 {
743 if osl_gate_resolve(target, 0 as *u8, ev[RP_E_GCONF] as *u8, ev[RP_E_ROOT_A] as *u8, RP_LIVE_SFX, ev[RP_E_ROOT_B] as *u8, RP_BUILT_SFX, d[RP_D_TRIED] as *u8, osl_tried_cap(), dst, gw1) == 1 {
744 let gl: i64 = rp_gate_name(gate, dst)
745 if gl > 0 { gate_ok = 1; d[RP_D_GATE_L] = gl; d[RP_D_GATE_WHY] = gw1[0] }
746 }
747 }
748 var jr_ok: i64 = 0
749 if ev[RP_E_GR_N] >= 0 { jr_ok = 1 }
750 d[RP_D_ROSTER_IN] = RCF_NONE
751 d[RP_D_GREEN_IN] = RCF_NONE
752 if gate_ok == 1 {
753 // ROSTERED: the gate is a row of ANY roster conf a beat runs, read the way that beat reads it (nx_rosterconf_lib)
754 d[RP_D_ROSTER_IN] = rcf_find(ev[RP_E_RT_P] as *i64, ev[RP_E_RT_K], gate, d[RP_D_GATE_L])
755 if d[RP_D_ROSTER_IN] >= 0 { d[RP_D_ROSTERED] = 1 }
756 // THE EVIDENCE: the production journal and the journal of every roster whose conf lists the gate -- a slow beat's
757 // GREEN is read where that beat writes it; a journal no roster listing the gate writes is never consulted
758 let rco: *i64 = d[RP_D_RCOUT] as *i64
759 rp_roster_evidence(ev, gate, d[RP_D_GATE_L], f[RGC_F_LAND], g, rco)
760 d[RP_D_GREEN_IN] = rco[RGC_RC_GREEN_IN]
761 jr_ok = 1 - rco[RGC_RC_UNREAD]
762 }
763 links[RGC_L_TESTED] = rgc_tested(gate_ok, jr_ok, g[RGC_G_GREEN], d[RP_D_ROSTERED], w)
764 why[RGC_L_TESTED] = w[0]
765 // WIRED: declaration first (a COMPLETE treat row), then the execution-surface census row of the subject tool
766 let eo: *i64 = d[RP_D_ESOUT] as *i64
767 var es_ok: i64 = 0
768 if ev[RP_E_ES_N] > 0 { es_ok = 1 }
769 if es_ok == 1 { if d[RP_D_TOOL_L] > 0 {
770 d[RP_D_ES_FOUND] = rgc_es_class(ev[RP_E_ES_P] as *u8, ev[RP_E_ES_N], tool, eo)
771 d[RP_D_SCLASS] = eo[RGC_ES_OUT_CLASS]
772 d[RP_D_ES_FO] = eo[RGC_ES_OUT_FO]
773 d[RP_D_ES_FL] = eo[RGC_ES_OUT_FL]
774 } }
775 links[RGC_L_WIRED] = rgc_wired(treat_complete, es_ok, d[RP_D_ES_FOUND], d[RP_D_SCLASS], w)
776 why[RGC_L_WIRED] = w[0]
777 // OPERATED: the newest unattended run of the subject -- the clock ran the program itself, the clock ran the treat row's
778 // agent command, or the roster ran the subject (a gate)
779 var run: i64 = CES_NONE
780 var src: i64 = 0
781 let al: *u8 = ev[RP_E_AL_P] as *u8
782 if d[RP_D_TOOL_L] > 0 {
783 if ev[RP_E_AX_ROWS] > 0 {
784 let r1: i64 = rgc_ax_newest_prog(al, ev[RP_E_AX_P] as *i64, ev[RP_E_AX_ROWS], tool)
785 if r1 > run { run = r1; src = RP_RUN_PROGRAM }
786 }
787 // the roster ran it: its newest roster row in the production journal or the journal of any roster listing it
788 rp_roster_evidence(ev, tool, d[RP_D_TOOL_L], CES_NONE, g2, d[RP_D_RCOUT] as *i64)
789 if g2[RGC_G_NEWEST] > run { run = g2[RGC_G_NEWEST]; src = RP_RUN_ROSTER }
790 }
791 var has_agent: i64 = 0
792 if agent_l > 0 {
793 let co: *i64 = d[RP_D_O2] as *i64
794 // the agent's command, from the plane the scheduler runs (RP_E_AGP); the agent EXISTS when either plane declares it
795 let cl: i64 = rgc_clock_cmd(ev[RP_E_AGP_P] as *u8, ev[RP_E_AGP_N], agent_o, agent_l, plan, co)
796 if cl > 0 { has_agent = 1 } else { if rgc_clock_cmd(ev[RP_E_CLOCK_P] as *u8, ev[RP_E_CLOCK_N], agent_o, agent_l, plan, off) > 0 { has_agent = 1 } }
797 if ev[RP_E_AX_ROWS] > 0 {
798 // THE WITNESS IS THE JOB NAME. The clock writes the forked path alone (clk_actlog), so a row running
799 // nx_plan_run.elf <plan> is named ONLY by the job= token the clock appends; the whole-command match stays as the
800 // fallback for a writer that carried the arguments
801 let rj: i64 = rgc_ax_newest_job(al, ev[RP_E_AX_P] as *i64, ev[RP_E_AX_ROWS], plan, agent_o, agent_l)
802 if rj > run { run = rj; src = RP_RUN_AGENT_JOB }
803 if cl > 0 {
804 let r2: i64 = rgc_ax_newest_cmd(al, ev[RP_E_AX_P] as *i64, ev[RP_E_AX_ROWS], ev[RP_E_AGP_P] as *u8, co[0], cl)
805 if r2 > run { run = r2; src = RP_RUN_AGENT }
806 }
807 }
808 }
809 d[RP_D_RUN] = run
810 d[RP_D_RUN_SRC] = src
811 d[RP_D_AGENT] = has_agent
812 var act_ok: i64 = 0
813 if ev[RP_E_AL_N] >= 0 { act_ok = 1 }
814 links[RGC_L_OPERATED] = rgc_operated(run, f[RGC_F_LAND], now, act_ok, d[RP_D_SCLASS], has_agent, w)
815 why[RGC_L_OPERATED] = w[0]
816 links[RGC_L_PAGE] = rgc_page(ev[RP_E_PAGE_S], f[RGC_F_LAND], w)
817 why[RGC_L_PAGE] = w[0]
818 // the climb, then the ceilings
819 d[RP_D_NISS] = rgc_issues(g, f[RGC_F_LAND], f[RGC_F_RETRACT], now, iss)
820 let stop: *i64 = d[RP_D_STOP] as *i64
821 let chain: i64 = rgc_state(declared, links, stop)
822 let stage: i64 = rgc_cap(chain, iss)
823 d[RP_D_CHAIN] = chain
824 d[RP_D_STAGE] = stage
825 d[RP_D_REWORK] = rgc_rework(iss)
826 var weak: i64 = stop[0]
827 if stage < chain { weak = rgc_weak_issue0() + rgc_capping_issue(chain, iss) }
828 d[RP_D_WEAK] = weak
829 return declared
830}
831
832func rp_epoch(v: i64) -> i64 {
833 if v < 0 { gw("none" as *u8); return 0 }
834 gn(v)
835 return 0
836}
837
838// the conf (which = RCF_PART_CONF) or the journal (RCF_PART_JRNL) of roster pair i, or none
839func rp_roster_part(ev: *i64, i: i64, which: i64) -> i64 {
840 if i < 0 { gw("none" as *u8); return 0 }
841 let lo: *i64 = sys_mmap(RGC_I64) as *i64
842 let ll: i64 = rcf_pair_part(ev[RP_E_RL_P] as *u8, ev[RP_E_RL_N], i, which, lo)
843 if ll <= 0 { gw("none" as *u8); return 0 }
844 gwb(ev[RP_E_RL_P] as *u8, lo[0], ll)
845 sys_munmap(lo as *u8, RGC_I64)
846 return 0
847}
848
849func rp_run_src_name(s: i64) -> *u8 {
850 if s == RP_RUN_PROGRAM { return "clock-ran-the-program" as *u8 }
851 if s == RP_RUN_AGENT { return "clock-ran-the-agent" as *u8 }
852 if s == RP_RUN_ROSTER { return "roster-ran-it" as *u8 }
853 if s == RP_RUN_AGENT_JOB { return "clock-ran-the-agent-by-its-job-name" as *u8 }
854 return "none" as *u8
855}
856
857func rp_agp_name(s: i64) -> *u8 {
858 if s == RP_AGP_LIVE { return "live" as *u8 }
859 if s == RP_AGP_DESIRED { return "desired-last-declaration" as *u8 }
860 return "none" as *u8
861}
862
863func rp_sclass_name(c: i64) -> *u8 {
864 if c == RGC_S_CLOCK { return "clock" as *u8 }
865 if c == RGC_S_ROSTER { return "roster" as *u8 }
866 if c == RGC_S_BLIND { return "unattended-unwitnessed" as *u8 }
867 if c == RGC_S_MCP { return "mcp-only" as *u8 }
868 return "none" as *u8
869}
870
871func rp_verdict_word(v: i64) -> *u8 {
872 if v == RGC_V_GREEN { return "GREEN" as *u8 }
873 if v == RGC_V_RED { return "RED" as *u8 }
874 return "OTHER" as *u8
875}
876
877func rp_link_line(name: *u8, v: i64, w: i64) -> i64 {
878 gw(" link " as *u8); gw(name); gw("=" as *u8); gw(rgc_hold_name(v)); gw(" why=" as *u8); gw(rgc_why_word(w))
879 return 0
880}
881
882func rp_print(dom: *u8, rung: *u8, ev: *i64, links: *i64, f: *i64, d: *i64) -> i64 {
883 let plan: *u8 = ev[RP_E_PLAN_P] as *u8
884 let why: *i64 = d[RP_D_WHY] as *i64
885 let g: *i64 = d[RP_D_GF] as *i64
886 let iss: *i64 = d[RP_D_ISS] as *i64
887 gw("RUNGCLOSE domain=" as *u8); gw(dom); gw(" rung=" as *u8); gw(rung); gw("\n" as *u8)
888 rp_link_line("journal" as *u8, links[RGC_L_JOURNAL], why[RGC_L_JOURNAL]); gw(" rows=" as *u8); gn(f[RGC_F_ROWS]); gw(" land=" as *u8); rp_epoch(f[RGC_F_LAND]); gw(" retract=" as *u8); rp_epoch(f[RGC_F_RETRACT]); gw("\n" as *u8)
889 rp_link_line("built" as *u8, links[RGC_L_BUILT], why[RGC_L_BUILT]); gw(" contract=" as *u8)
890 if rgc_has_contract(plan, f) == 1 { gwb(plan, f[RGC_F_SYM_O], f[RGC_F_SYM_L]) } else { gw("none-declared" as *u8) }
891 gw(" matrix_row=" as *u8); gn(d[RP_D_RESOLVED])
892 if d[RP_D_RESOLVED] == 1 { gw(" organ=" as *u8); gwb(ev[RP_E_MX_P] as *u8, d[RP_D_ORGAN_O], d[RP_D_ORGAN_L]); gw(" symbol_ruler=" as *u8); gn(d[RP_D_SD]) }
893 gw("\n" as *u8)
894 rp_link_line("live" as *u8, links[RGC_L_LIVE], why[RGC_L_LIVE]); gw(" tool=" as *u8)
895 if d[RP_D_TOOL_L] > 0 { gw(d[RP_D_TOOL] as *u8) } else { gw("none" as *u8) }
896 gw(" treat_row=" as *u8)
897 if d[RP_D_TREAT_ROW] >= 0 { gw("1 mcp=" as *u8); gw(trt_state_name(d[RP_D_MCP])) } else { gw("0" as *u8) }
898 gw("\n" as *u8)
899 rp_link_line("tested" as *u8, links[RGC_L_TESTED], why[RGC_L_TESTED]); gw(" gate=" as *u8)
900 if d[RP_D_GATE_L] > 0 {
901 gw(d[RP_D_GATE] as *u8); gw(" resolved_by=" as *u8)
902 if d[RP_D_GATE_WHY] == RP_GW_RUNG { gw(RP_GW_RUNG_NAME) } else { gw(osl_why_name(d[RP_D_GATE_WHY])) }
903 gw(" rostered=" as *u8); gn(d[RP_D_ROSTERED]); gw(" rostered_in=" as *u8); rp_roster_part(ev, d[RP_D_ROSTER_IN], RCF_PART_CONF)
904 gw(" green_in=" as *u8); rp_roster_part(ev, d[RP_D_GREEN_IN], RCF_PART_JRNL)
905 gw(" roster_confs_read=" as *u8); gn(ev[RP_E_RT_READ]); gw("/" as *u8); gn(ev[RP_E_RT_DISTINCT])
906 gw(" roster_journals_read=" as *u8); gn(ev[RP_E_RJ_READ]); gw("/" as *u8); gn(ev[RP_E_RJ_K]); gw(" roster_beats_other_journal=" as *u8); gn(ev[RP_E_R_OTHER])
907 gw(" roster_rows=" as *u8); gn(g[RGC_G_ROWS]); gw(" newest=" as *u8); rp_epoch(g[RGC_G_NEWEST]); gw(" newest_verdict=" as *u8); gw(rp_verdict_word(g[RGC_G_NEWEST_V]))
908 gw(" green_since_land=" as *u8); rp_epoch(g[RGC_G_GREEN]); gw(" turns_since_land=" as *u8); gn(g[RGC_G_FLIPS])
909 } else { gw("none tried=" as *u8); gw(d[RP_D_TRIED] as *u8) }
910 gw("\n" as *u8)
911 rp_link_line("wired" as *u8, links[RGC_L_WIRED], why[RGC_L_WIRED]); gw(" treat=" as *u8)
912 if d[RP_D_TREAT_ROW] >= 0 { gw(trt_verdict_name(d[RP_D_TREAT_V])) } else { gw("NO-ROW" as *u8) }
913 gw(" surface_census_row=" as *u8); gn(d[RP_D_ES_FOUND]); gw(" surface=" as *u8); gw(rp_sclass_name(d[RP_D_SCLASS]))
914 if d[RP_D_ES_FL] > 0 { gw(" first_surface=" as *u8); gwb(ev[RP_E_ES_P] as *u8, d[RP_D_ES_FO], d[RP_D_ES_FL]) }
915 gw("\n" as *u8)
916 rp_link_line("operated" as *u8, links[RGC_L_OPERATED], why[RGC_L_OPERATED]); gw(" run=" as *u8); rp_epoch(d[RP_D_RUN]); gw(" read_from=" as *u8); gw(rp_run_src_name(d[RP_D_RUN_SRC]))
917 gw(" agent=" as *u8); gn(d[RP_D_AGENT]); gw(" agent_plane=" as *u8); gw(rp_agp_name(ev[RP_E_AGP_SRC])); gw(" margin_s=" as *u8); gn(rgc_margin_s()); gw(" actlog_window_covers_margin=" as *u8); gn(ev[RP_E_AL_COVER]); gw(" clock_runs_indexed=" as *u8); gn(ev[RP_E_AX_ROWS])
918 gw("\n" as *u8)
919 rp_link_line("page" as *u8, links[RGC_L_PAGE], why[RGC_L_PAGE]); gw(" page_mtime=" as *u8); rp_epoch(ev[RP_E_PAGE_S]); gw("\n" as *u8)
920 let iw: *u8 = sys_mmap(rgc_issue_word_bound() + 1)
921 rgc_issue_word(iss, iw, 0)
922 gw(" issues=" as *u8); gw(iw); gw(" rework=" as *u8); gn(d[RP_D_REWORK]); gw("\n" as *u8)
923 var k: i64 = 0
924 while k < RGC_ISSUES { if iss[k] == 1 { gw(" " as *u8); gw(rgc_issue_name(k)); gw(": " as *u8); gw(rgc_issue_need(k)); if k == RGC_I_STALE { rp_roster_confs_tail(ev) } gw("\n" as *u8) } k = k + 1 }
925 gw("RUNGCLOSE stage=" as *u8); gw(rgc_state_name(d[RP_D_STAGE])); gw(" links_earn=" as *u8); gw(rgc_state_name(d[RP_D_CHAIN])); gw(" weakest=" as *u8); gw(rgc_weak_name(d[RP_D_WEAK]))
926 let wk: i64 = d[RP_D_WEAK]
927 if wk < RGC_LINKS { gw(" weakest_reads=" as *u8); gw(rgc_hold_name(links[wk])); gw(" need=" as *u8); gw(rgc_why_need(why[wk])); if why[wk] == RGC_W_UNROSTERED { rp_roster_confs_tail(ev) } }
928 if wk >= rgc_weak_issue0() { gw(" need=" as *u8); gw(rgc_issue_need(wk - rgc_weak_issue0())); if wk - rgc_weak_issue0() == RGC_I_STALE { rp_roster_confs_tail(ev) } }
929 return 0
930}
931
932func rp_exit_for(links: *i64, d: *i64) -> i64 {
933 if d[RP_D_STAGE] == RGC_ST_PRODUCTION { return RP_EXIT_DONE }
934 if d[RP_D_STAGE] == RGC_ST_NORUNG { return RP_EXIT_NOT }
935 let wk: i64 = d[RP_D_WEAK]
936 if wk < RGC_LINKS { if links[wk] == RGC_UNOBS { return RP_EXIT_UNOBS } }
937 return RP_EXIT_NOT
938}
939
940func rp_exit_word(x: i64) -> *u8 {
941 if x == RP_EXIT_DONE { return "PRODUCTION" as *u8 }
942 if x == RP_EXIT_UNOBS { return "UNOBSERVABLE" as *u8 }
943 return "NOT-PRODUCTION" as *u8
944}
945
946func rp_usage() -> i64 {
947 gw("usage: nx_rungclose check <domain> <rung> [anchor] | census <domain-or-all> [anchor] | journal <domain> <rung> <measure|land|retract|lesson|queue|done> <text> [anchor] | close|reopen (RETIRED: prints the check, writes nothing)\n" as *u8)
948 gw("exit 0 PRODUCTION, CENSUSED or JOURNALED | 1 below PRODUCTION or REFUSED | 2 usage | 3 UNOBSERVABLE or NO-PLAN\n" as *u8)
949 return RP_EXIT_USAGE
950}
951
952// the bytes one ledger row needs beyond its domain and rung: epoch, two stage words, the actor, the issue word, the
953// rework digit, the pipes and keys -- derived
954func rp_move_row_fixed() -> i64 {
955 var longest: i64 = 0
956 var s: i64 = 0
957 while s < RGC_STATES { let l: i64 = ces_slen(rgc_state_name(s)); if l > longest { longest = l } s = s + 1 }
958 return CES_NUM_CAP + longest * 2 + ces_slen("NEW>|" as *u8) + ces_slen(RP_CENSUS_ACTOR) + ces_slen("|||issues=|rework=" as *u8) + rgc_issue_word_bound() + CES_NUM_CAP + 2
959}
960
961// ONE BOARD: the stage of every rung; worklist rows, snapshot rows and ledger rows into buffers sized from the board.
962// prev carries the last census's snapshot (RP_P_*). Totals accumulate into tot.
963func rp_census(dom: *u8, ev: *i64, quiet: i64, tot: *i64, wk: *i64, prev: *i64, d: *i64) -> i64 {
964 let plan: *u8 = ev[RP_E_PLAN_P] as *u8
965 let pn: i64 = ev[RP_E_PLAN_N]
966 let off: *i64 = sys_mmap(RGC_I64) as *i64
967 var nr: i64 = 0
968 var pp: i64 = 0
969 while pp < pn {
970 let pe: i64 = ces_line_end(plan, pn, pp)
971 let pl0: i64 = ces_field(plan, pp, pe, 0, off)
972 if ces_span_is(plan, off[0], pl0, CES_RUNG_TAG) == 1 { if ces_nfields(plan, pp, pe) >= CES_RUNG_NF { nr = nr + 1 } }
973 pp = pe + 1
974 }
975 // every row is its rung id (a span of the plan, so the ids sum to at most pn) plus the domain plus a fixed part
976 let dl: i64 = ces_slen(dom)
977 let wbuf: *u8 = sys_mmap(pn + nr * (dl + rgc_work_row_fixed()) + 1)
978 let sbuf: *u8 = sys_mmap(pn + nr * (dl + rgc_stage_row_fixed()) + 1)
979 let mbuf: *u8 = sys_mmap(pn + nr * (dl + rp_move_row_fixed()) + 1)
980 var wo: i64 = 0
981 var so: i64 = 0
982 var mo: i64 = 0
983 var wrows: i64 = 0
984 let counts: *i64 = sys_mmap(RGC_STATES * RGC_I64) as *i64
985 let links: *i64 = sys_mmap(RGC_LINKS * RGC_I64) as *i64
986 let f: *i64 = sys_mmap(RGC_F_SLOTS * RGC_I64) as *i64
987 let pout: *i64 = sys_mmap(RGC_SG_OUT_SLOTS * RGC_I64) as *i64
988 let iw: *u8 = sys_mmap(rgc_issue_word_bound() + 1)
989 let iss: *i64 = d[RP_D_ISS] as *i64
990 let now: i64 = ev[RP_E_NOW]
991 var rungs: i64 = 0
992 var unobs: i64 = 0
993 var dups: i64 = 0
994 var rework: i64 = 0
995 var p: i64 = 0
996 while p < pn {
997 let e: i64 = ces_line_end(plan, pn, p)
998 let l0: i64 = ces_field(plan, p, e, 0, off)
999 if ces_span_is(plan, off[0], l0, CES_RUNG_TAG) == 1 { if ces_nfields(plan, p, e) >= CES_RUNG_NF {
1000 let il: i64 = ces_field(plan, p, e, CES_RUNG_F_ID, off)
1001 let rid: *u8 = rp_span_copy(plan, off[0], il)
1002 rp_eval(rid, ev, links, f, d)
1003 let stage: i64 = d[RP_D_STAGE]
1004 counts[stage] = counts[stage] + 1
1005 rungs = rungs + 1
1006 var isun: i64 = 0
1007 if d[RP_D_WEAK] < RGC_LINKS { if links[d[RP_D_WEAK]] == RGC_UNOBS { isun = 1 } }
1008 unobs = unobs + isun
1009 if f[RGC_F_DECLS] > 1 { dups = dups + 1 }
1010 rework = rework + d[RP_D_REWORK]
1011 var k: i64 = 0
1012 while k < RGC_ISSUES { tot[RGC_STATES + RP_T_ISS0 + k] = tot[RGC_STATES + RP_T_ISS0 + k] + iss[k]; k = k + 1 }
1013 if rgc_work_member(stage) == 1 {
1014 wo = rgc_work_row(wbuf, wo, stage, dom, rid, d[RP_D_WEAK], isun, f[RGC_F_LAST], f[RGC_F_COST])
1015 wrows = wrows + 1
1016 }
1017 // THE CLOSER: the stage against the last census's. Unchanged stage and issues keep their since; a move is a row.
1018 rgc_issue_word(iss, iw, 0)
1019 var since: i64 = now
1020 var from: i64 = 0 - 1
1021 var found: i64 = 0
1022 if prev[RP_P_BASELINE] == 0 {
1023 found = rgc_stage_find(prev[RP_P_BUF] as *u8, prev[RP_P_N], dom, rid, prev[RP_P_CUR] as *i64, pout)
1024 if found == 1 {
1025 from = pout[RGC_SG_OUT_STATE]
1026 if from == stage { if ces_span_is(prev[RP_P_BUF] as *u8, pout[RGC_SG_OUT_IO], pout[RGC_SG_OUT_IL], iw) == 1 { if pout[RGC_SG_OUT_SINCE] > 0 { since = pout[RGC_SG_OUT_SINCE] } } }
1027 }
1028 }
1029 so = rgc_stage_row(sbuf, so, dom, rid, stage, iss, since, d[RP_D_REWORK])
1030 var moved: i64 = 0
1031 if found == 1 { if from != stage { moved = 1 } }
1032 if prev[RP_P_BASELINE] == 0 { if found == 0 { if stage >= RGC_ST_STARTED { moved = 1; tot[RGC_STATES + RP_T_NEW] = tot[RGC_STATES + RP_T_NEW] + 1 } } }
1033 if moved == 1 {
1034 if found == 1 {
1035 if stage > from { tot[RGC_STATES + RP_T_PROMOTED] = tot[RGC_STATES + RP_T_PROMOTED] + 1 }
1036 if stage < from { tot[RGC_STATES + RP_T_DEMOTED] = tot[RGC_STATES + RP_T_DEMOTED] + 1 }
1037 }
1038 if stage == RGC_ST_PRODUCTION { if from != RGC_ST_PRODUCTION { tot[RGC_STATES + RP_T_ENTERED] = tot[RGC_STATES + RP_T_ENTERED] + 1 } }
1039 mo = ces_fmt_int(mbuf, mo, now)
1040 mo = rgc_work_pipe(mbuf, mo)
1041 mo = ces_cat(mbuf, mo, dom)
1042 mo = rgc_work_pipe(mbuf, mo)
1043 mo = ces_cat(mbuf, mo, rid)
1044 mo = rgc_work_pipe(mbuf, mo)
1045 mo = rgc_move_word(mbuf, mo, from, stage)
1046 mo = rgc_work_pipe(mbuf, mo)
1047 mo = ces_cat(mbuf, mo, RP_CENSUS_ACTOR)
1048 mo = ces_cat(mbuf, mo, "|issues=" as *u8)
1049 mo = ces_cat(mbuf, mo, iw)
1050 mo = ces_cat(mbuf, mo, "|rework=" as *u8)
1051 mo = ces_fmt_int(mbuf, mo, d[RP_D_REWORK])
1052 mbuf[mo] = CES_CH_NL as u8
1053 mo = mo + 1
1054 }
1055 if quiet == 0 {
1056 gw(" rung=" as *u8); gw(rid); gw(" stage=" as *u8); gw(rgc_state_name(stage)); gw(" weakest=" as *u8); gw(rgc_weak_name(d[RP_D_WEAK]))
1057 if isun == 1 { gw(" (UNOBSERVABLE)" as *u8) }
1058 if d[RP_D_NISS] > 0 { gw(" issues=" as *u8); gw(iw) }
1059 gw("\n" as *u8)
1060 }
1061 } }
1062 p = e + 1
1063 }
1064 var sum: i64 = 0
1065 var s: i64 = 0
1066 gw("RUNGCLOSE-CENSUS domain=" as *u8); gw(dom); gw(" rungs=" as *u8); gn(rungs)
1067 while s < RGC_STATES {
1068 if s != RGC_ST_NORUNG { gw(" " as *u8); gw(rgc_state_name(s)); gw("=" as *u8); gn(counts[s]) }
1069 sum = sum + counts[s]
1070 s = s + 1
1071 }
1072 gw(" sum=" as *u8); gn(sum)
1073 gw(" started_not_production=" as *u8); gn(rungs - counts[RGC_ST_OPEN] - counts[RGC_ST_PRODUCTION])
1074 gw(" weakest_unobservable=" as *u8); gn(unobs)
1075 gw(" rework=" as *u8); gn(rework)
1076 gw(" duplicate_id_rows=" as *u8); gn(dups)
1077 gw(" verdict=CENSUSED\n" as *u8)
1078 var ts: i64 = 0
1079 while ts < RGC_STATES { tot[ts] = tot[ts] + counts[ts]; ts = ts + 1 }
1080 tot[RGC_STATES + RP_T_RUNGS] = tot[RGC_STATES + RP_T_RUNGS] + rungs
1081 tot[RGC_STATES + RP_T_UNOBS] = tot[RGC_STATES + RP_T_UNOBS] + unobs
1082 tot[RGC_STATES + RP_T_DUPS] = tot[RGC_STATES + RP_T_DUPS] + dups
1083 tot[RGC_STATES + RP_T_REWORK] = tot[RGC_STATES + RP_T_REWORK] + rework
1084 wk[RP_W_BUF] = wbuf as i64
1085 wk[RP_W_FILL] = wo
1086 wk[RP_W_ROWS] = wrows
1087 wk[RP_W_SBUF] = sbuf as i64
1088 wk[RP_W_SFILL] = so
1089 wk[RP_W_MBUF] = mbuf as i64
1090 wk[RP_W_MFILL] = mo
1091 return RP_EXIT_DONE
1092}
1093
1094// an artifact path: the production name, or the anchor plus a suffix for a non-default run
1095func rp_art_path(anchor: *u8, isdefault: i64, prodname: *u8, sfx: *u8) -> *u8 {
1096 let p: *u8 = sys_mmap(ces_slen(anchor) + ces_slen(prodname) + ces_slen(sfx) + ces_slen(RP_TMP_SFX) + 1)
1097 if isdefault == 1 { ces_cat(p, 0, prodname) } else { var o: i64 = ces_cat(p, 0, anchor); ces_cat(p, o, sfx) }
1098 return p
1099}
1100
1101func rp_tmp_path(path: *u8) -> *u8 {
1102 let t: *u8 = sys_mmap(ces_slen(path) + ces_slen(RP_TMP_SFX) + 1)
1103 var o: i64 = ces_cat(t, 0, path)
1104 ces_cat(t, o, RP_TMP_SFX)
1105 return t
1106}
1107
1108// every board on the published list, one summary line each, then the FLEET line -- written as ONE canonical status line
1109// for the digest and the hub to pass through -- plus the worklist, the stage snapshot and the moves
1110func rp_census_all(anchor: *u8, isdefault: i64) -> i64 {
1111 let path: *u8 = sys_mmap(CT_PATH_CAP)
1112 let lenp: *i64 = sys_mmap(RGC_I64) as *i64
1113 lenp[0] = 0
1114 ct_build_path(ct_first_published(), "regen" as *u8, ".list" as *u8, path)
1115 var lst: *u8 = sys_read_file(path, lenp)
1116 if lenp[0] <= 0 {
1117 ct_build_path(ct_second_published(), "regen" as *u8, ".list" as *u8, path)
1118 lst = sys_read_file(path, lenp)
1119 }
1120 if lenp[0] <= 0 { gw("RUNGCLOSE-FLEET regen.list UNREADABLE in both trees verdict=NO-POPULATION\n" as *u8); return RP_EXIT_UNOBS }
1121 let ln: i64 = lenp[0]
1122 let ev: *i64 = sys_mmap(RP_E_SLOTS * RGC_I64) as *i64
1123 rp_gather_shared(ev)
1124 let now: i64 = ev[RP_E_NOW]
1125 let d: *i64 = rp_detail_new()
1126 let tot: *i64 = sys_mmap((RGC_STATES + RP_T_EXTRA) * RGC_I64) as *i64
1127 let wk: *i64 = sys_mmap(RP_W_SLOTS * RGC_I64) as *i64
1128 // the previous snapshot: ABSENT means this census is the BASELINE -- it records every stage and moves nothing
1129 let spath: *u8 = rp_art_path(anchor, isdefault, RP_STAGES, RP_STAGES_SFX)
1130 let prev: *i64 = sys_mmap(RP_P_SLOTS * RGC_I64) as *i64
1131 let pnp: *i64 = sys_mmap(RGC_I64) as *i64
1132 pnp[0] = 0
1133 let pbuf: *u8 = sys_read_file(spath, pnp)
1134 prev[RP_P_BUF] = pbuf as i64
1135 prev[RP_P_N] = pnp[0]
1136 let cur: *i64 = sys_mmap(RGC_I64) as *i64
1137 cur[0] = 0
1138 prev[RP_P_CUR] = cur as i64
1139 prev[RP_P_BASELINE] = 0
1140 if pnp[0] <= 0 { prev[RP_P_BASELINE] = 1 }
1141 // the three whole-file artifacts: temp beside the final name, renamed over it only when every write landed
1142 let wpath: *u8 = rp_art_path(anchor, isdefault, RP_WORKLIST, RP_WORKLIST_SFX)
1143 let wtmp: *u8 = rp_tmp_path(wpath)
1144 let stmp: *u8 = rp_tmp_path(spath)
1145 let wfd: i64 = sys_openat_wr(wtmp, RP_MODE_644)
1146 let sfd: i64 = sys_openat_wr(stmp, RP_MODE_644)
1147 var wshort: i64 = 0
1148 var sshort: i64 = 0
1149 var wbytes: i64 = 0
1150 var sbytes: i64 = 0
1151 if wfd >= 0 {
1152 let hb: *u8 = sys_mmap(rgc_work_head_bound())
1153 let hn: i64 = rgc_work_head(hb, 0, now)
1154 if sys_write(wfd, hb, hn) != hn { wshort = 1 }
1155 wbytes = hn
1156 }
1157 if sfd >= 0 {
1158 let sb: *u8 = sys_mmap(rgc_stage_head_bound())
1159 let sn: i64 = rgc_stage_head(sb, 0, now)
1160 if sys_write(sfd, sb, sn) != sn { sshort = 1 }
1161 sbytes = sn
1162 }
1163 let lpath: *u8 = rp_art_path(anchor, isdefault, RP_LEDGER, RP_LEDGER_SFX)
1164 var lfd: i64 = 0 - 1
1165 var lrows: i64 = 0
1166 var lshort: i64 = 0
1167 var wrows: i64 = 0
1168 var boards: i64 = 0
1169 var noplan: i64 = 0
1170 var p: i64 = 0
1171 while p < ln {
1172 let e: i64 = ces_line_end(lst, ln, p)
1173 let l: i64 = e - p
1174 if l > 0 { if lst[p] != (CES_CH_HASH as u8) {
1175 let name: *u8 = rp_span_copy(lst, p, l)
1176 if rp_gather_board(name, ev) == 1 {
1177 boards = boards + 1
1178 rp_census(name, ev, 1, tot, wk, prev, d)
1179 wrows = wrows + wk[RP_W_ROWS]
1180 if wfd >= 0 { if wk[RP_W_FILL] > 0 {
1181 if sys_write(wfd, wk[RP_W_BUF] as *u8, wk[RP_W_FILL]) != wk[RP_W_FILL] { wshort = 1 }
1182 wbytes = wbytes + wk[RP_W_FILL]
1183 } }
1184 if sfd >= 0 { if wk[RP_W_SFILL] > 0 {
1185 if sys_write(sfd, wk[RP_W_SBUF] as *u8, wk[RP_W_SFILL]) != wk[RP_W_SFILL] { sshort = 1 }
1186 sbytes = sbytes + wk[RP_W_SFILL]
1187 } }
1188 if wk[RP_W_MFILL] > 0 {
1189 if lfd < 0 { lfd = sys_openat_append(lpath, RP_MODE_644) }
1190 if lfd >= 0 {
1191 let mb: *u8 = wk[RP_W_MBUF] as *u8
1192 if sys_write(lfd, mb, wk[RP_W_MFILL]) != wk[RP_W_MFILL] { lshort = 1 }
1193 var q: i64 = 0
1194 while q < wk[RP_W_MFILL] { if mb[q] == (CES_CH_NL as u8) { lrows = lrows + 1 } q = q + 1 }
1195 } else { lshort = 1 }
1196 }
1197 } else { noplan = noplan + 1 }
1198 } }
1199 p = e + 1
1200 }
1201 if lfd >= 0 { sys_close(lfd) }
1202 let base: i64 = RGC_STATES
1203 // the fleet line, bounded by the digest's row budget: the stage partition with its sum, the rework count and the moves
1204 let fl: *u8 = sys_mmap(RP_PATH_CAP * 2)
1205 var fo: i64 = ces_cat(fl, 0, "RUNGCLOSE-FLEET asof=" as *u8)
1206 fo = ces_fmt_int(fl, fo, now)
1207 fo = ces_cat(fl, fo, " boards=" as *u8)
1208 fo = ces_fmt_int(fl, fo, boards)
1209 fo = ces_cat(fl, fo, " rungs=" as *u8)
1210 fo = ces_fmt_int(fl, fo, tot[base + RP_T_RUNGS])
1211 var sum: i64 = 0
1212 var s: i64 = RGC_ST_OPEN
1213 while s < RGC_STATES {
1214 fo = ces_cat(fl, fo, " " as *u8)
1215 fo = ces_cat(fl, fo, rgc_state_name(s))
1216 fo = ces_cat(fl, fo, "=" as *u8)
1217 fo = ces_fmt_int(fl, fo, tot[s])
1218 sum = sum + tot[s]
1219 s = s + 1
1220 }
1221 fo = ces_cat(fl, fo, " sum=" as *u8)
1222 fo = ces_fmt_int(fl, fo, sum)
1223 fo = ces_cat(fl, fo, " rework=" as *u8)
1224 fo = ces_fmt_int(fl, fo, tot[base + RP_T_REWORK])
1225 fo = ces_cat(fl, fo, " moved=" as *u8)
1226 fo = ces_fmt_int(fl, fo, lrows)
1227 fo = ces_cat(fl, fo, " verdict=CENSUSED\n" as *u8)
1228 let sp: *u8 = rp_art_path(anchor, isdefault, RP_STATUS, RP_STATUS_SFX)
1229 let stfd: i64 = sys_openat_wr(sp, RP_MODE_644)
1230 var swrote: i64 = 0
1231 if stfd >= 0 { sys_write(stfd, fl, fo); sys_close(stfd); swrote = 1 }
1232 gw("RUNGCLOSE-STATUS path=" as *u8); gw(sp); gw(" wrote=" as *u8); gn(swrote); gw("\n" as *u8)
1233 // a torn list is never published: on a short write the temp stays beside the old list and the old list stands.
1234 // rows against started_not_production is two counters of one population: they reconcile or the line says they do not.
1235 var wlanded: i64 = 0
1236 if wfd >= 0 {
1237 sys_close(wfd)
1238 if wshort == 0 { if sys_renameat(wtmp, wpath) == 0 { wlanded = 1 } }
1239 }
1240 var slanded: i64 = 0
1241 if sfd >= 0 {
1242 sys_close(sfd)
1243 if sshort == 0 { if sys_renameat(stmp, spath) == 0 { slanded = 1 } }
1244 }
1245 let snp: i64 = tot[base + RP_T_RUNGS] - tot[RGC_ST_OPEN] - tot[RGC_ST_PRODUCTION]
1246 var recon: i64 = 0
1247 if wrows == snp { recon = 1 }
1248 gw("RUNGCLOSE-WORKLIST path=" as *u8); gw(wpath); gw(" rows=" as *u8); gn(wrows); gw(" started_not_production=" as *u8); gn(snp); gw(" reconciled=" as *u8); gn(recon)
1249 gw(" bytes=" as *u8); gn(wbytes); gw(" short_write=" as *u8); gn(wshort); gw(" landed=" as *u8); gn(wlanded); gw("\n" as *u8)
1250 gw("RUNGCLOSE-STAGES path=" as *u8); gw(spath); gw(" rungs=" as *u8); gn(tot[base + RP_T_RUNGS]); gw(" bytes=" as *u8); gn(sbytes); gw(" short_write=" as *u8); gn(sshort); gw(" landed=" as *u8); gn(slanded)
1251 gw(" baseline=" as *u8); gn(prev[RP_P_BASELINE]); gw("\n" as *u8)
1252 gw("RUNGCLOSE-MOVES ledger=" as *u8); gw(lpath); gw(" rows=" as *u8); gn(lrows); gw(" promoted=" as *u8); gn(tot[base + RP_T_PROMOTED]); gw(" demoted=" as *u8); gn(tot[base + RP_T_DEMOTED])
1253 gw(" entered_production=" as *u8); gn(tot[base + RP_T_ENTERED]); gw(" new=" as *u8); gn(tot[base + RP_T_NEW]); gw(" short_write=" as *u8); gn(lshort)
1254 if prev[RP_P_BASELINE] == 1 { gw(" (BASELINE: no previous snapshot, so nothing can have moved -- the next census diffs against this one)" as *u8) }
1255 gw("\n" as *u8)
1256 gw("RUNGCLOSE-ISSUES" as *u8)
1257 var k: i64 = 0
1258 while k < RGC_ISSUES { gw(" " as *u8); gw(rgc_issue_name(k)); gw("=" as *u8); gn(tot[base + RP_T_ISS0 + k]); k = k + 1 }
1259 gw(" rework=" as *u8); gn(tot[base + RP_T_REWORK]); gw(" weakest_unobservable=" as *u8); gn(tot[base + RP_T_UNOBS]); gw(" duplicate_id_rows=" as *u8); gn(tot[base + RP_T_DUPS])
1260 gw(" noplan=" as *u8); gn(noplan); gw("\n" as *u8)
1261 gw("RUNGCLOSE-EVIDENCE roster_rows_indexed=" as *u8); gn(ev[RP_E_GX_ROWS]); gw(" surface_census_bytes=" as *u8); gn(ev[RP_E_ES_N]); gw(" actlog_bytes=" as *u8); gn(ev[RP_E_AL_BYTES])
1262 gw(" actlog_window_bytes=" as *u8); gn(ev[RP_E_AL_N]); gw(" actlog_window_covers_margin=" as *u8); gn(ev[RP_E_AL_COVER]); gw(" clock_runs_indexed=" as *u8); gn(ev[RP_E_AX_ROWS])
1263 gw(" agent_plane=" as *u8); gw(rp_agp_name(ev[RP_E_AGP_SRC])); gw(" roster_beats=" as *u8); gn(ev[RP_E_R_BEATS]); gw(" roster_beats_other_journal=" as *u8); gn(ev[RP_E_R_OTHER]); gw(" roster_confs_read=" as *u8); gn(ev[RP_E_RT_READ]); gw("/" as *u8); gn(ev[RP_E_RT_DISTINCT]); gw(" roster_journals_read=" as *u8); gn(ev[RP_E_RJ_READ]); gw("/" as *u8); gn(ev[RP_E_RJ_K]); rp_roster_confs_tail(ev); gw("\n" as *u8)
1264 gwb(fl, 0, fo)
1265 return RP_EXIT_DONE
1266}
1267
1268// the path of the board's plan in the tree that answers, in the resolver's published order; 1 when one opens
1269func rp_plan_path(dom: *u8, path: *u8) -> i64 {
1270 ct_build_path(ct_first_published(), dom, BF_PLAN_SUFFIX, path)
1271 let f1: i64 = sys_openat_rd(path)
1272 if f1 >= 0 { sys_close(f1); return 1 }
1273 ct_build_path(ct_second_published(), dom, BF_PLAN_SUFFIX, path)
1274 let f2: i64 = sys_openat_rd(path)
1275 if f2 >= 0 { sys_close(f2); return 1 }
1276 return 0
1277}
1278
1279// JOURNAL (EC55 item 3): one row appended to the board's own plan BY GRAMMAR -- the rung is declared (main refuses NORUNG
1280// before this), the kind is one the boards render, the text cannot break the row, and the row starts a line. ONE O_APPEND
1281// write, so a concurrent writer can interleave rows and never bytes. Returns 1 appended, 0 refused (the reason is printed).
1282func rp_journal(rung: *u8, kind: *u8, text: *u8, dom: *u8, ev: *i64) -> i64 {
1283 let off: *i64 = sys_mmap(RGC_I64) as *i64
1284 if rgc_kind_ok(kind) == 0 {
1285 gw("JOURNAL REFUSED kind=" as *u8); gw(kind); gw(" is not a kind the boards render: measure, land, retract, lesson, queue or done\n" as *u8)
1286 return 0
1287 }
1288 let tk: i64 = rgc_text_ok(text, off)
1289 if tk == 0 { gw("JOURNAL REFUSED the text is empty: a row that says nothing is not a journal row\n" as *u8); return 0 }
1290 if tk != 1 {
1291 gw("JOURNAL REFUSED the text carries a pipe or a line break at byte " as *u8); gn(off[0]); gw(": a pipe invents a field and a line break invents a row\n" as *u8)
1292 return 0
1293 }
1294 let path: *u8 = sys_mmap(CT_PATH_CAP)
1295 if rp_plan_path(dom, path) == 0 { gw("JOURNAL REFUSED the board's plan could not be opened in either tree\n" as *u8); return 0 }
1296 let plan: *u8 = ev[RP_E_PLAN_P] as *u8
1297 let pn: i64 = ev[RP_E_PLAN_N]
1298 var lead: i64 = 0
1299 if pn > 0 { if plan[pn - 1] != (CES_CH_NL as u8) { lead = 1 } }
1300 let row: *u8 = sys_mmap(rgc_log_row_bound(rung, kind, text))
1301 let rn: i64 = rgc_log_row(row, 0, lead, sys_now_realtime_sec(), rung, kind, text)
1302 let fd: i64 = sys_openat_append(path, RP_MODE_644)
1303 if fd < 0 { gw("JOURNAL REFUSED the plan could not be opened for append: " as *u8); gw(path); gw("\n" as *u8); return 0 }
1304 let w: i64 = sys_write(fd, row, rn)
1305 sys_close(fd)
1306 gw("JOURNAL path=" as *u8); gw(path); gw(" kind=" as *u8); gw(kind); gw(" row_bytes=" as *u8); gn(rn); gw(" wrote=" as *u8); gn(w)
1307 if w == rn { gw(" appended=1\n" as *u8); return 1 }
1308 gw(" appended=0 SHORT-WRITE: read the plan's tail before journaling again\n" as *u8)
1309 return 0
1310}
1311
1312func main(argc: i64, argv: *i64) -> i64 {
1313 if argc < 3 { return rp_usage() }
1314 let verb: *u8 = argv[1] as *u8
1315 let dom: *u8 = argv[2] as *u8
1316 var ischeck: i64 = 0
1317 var iscensus: i64 = 0
1318 var isjournal: i64 = 0
1319 var isretired: i64 = 0
1320 if ces_span_is(verb, 0, ces_slen(verb), "check" as *u8) == 1 { ischeck = 1 }
1321 if ces_span_is(verb, 0, ces_slen(verb), "census" as *u8) == 1 { iscensus = 1 }
1322 if ces_span_is(verb, 0, ces_slen(verb), "journal" as *u8) == 1 { isjournal = 1 }
1323 if ces_span_is(verb, 0, ces_slen(verb), "close" as *u8) == 1 { isretired = 1 }
1324 if ces_span_is(verb, 0, ces_slen(verb), "reopen" as *u8) == 1 { isretired = 1 }
1325 if ischeck + iscensus + isjournal + isretired == 0 { return rp_usage() }
1326 var anchor: *u8 = RP_ANCHOR_DEFAULT
1327 var isdefault: i64 = 1
1328 var aarg: i64 = 4
1329 if isretired == 1 { aarg = 5 }
1330 if iscensus == 1 { aarg = 3 }
1331 if isjournal == 1 { aarg = 6 }
1332 if isjournal == 1 { if argc < 6 { return rp_usage() } }
1333 if argc > aarg { anchor = argv[aarg] as *u8; isdefault = 0 }
1334 if iscensus == 0 { if argc < 4 { return rp_usage() } }
1335 if iscensus == 1 { if ces_span_is(dom, 0, ces_slen(dom), "all" as *u8) == 1 { return rp_census_all(anchor, isdefault) } }
1336 let ev: *i64 = sys_mmap(RP_E_SLOTS * RGC_I64) as *i64
1337 rp_gather_shared(ev)
1338 if rp_gather_board(dom, ev) == 0 {
1339 gw("RUNGCLOSE domain=" as *u8); gw(dom); gw(" plan=UNREADABLE-IN-BOTH-TREES verdict=NO-PLAN\n" as *u8)
1340 return RP_EXIT_UNOBS
1341 }
1342 let d: *i64 = rp_detail_new()
1343 if iscensus == 1 {
1344 let tot1: *i64 = sys_mmap((RGC_STATES + RP_T_EXTRA) * RGC_I64) as *i64
1345 let wk1: *i64 = sys_mmap(RP_W_SLOTS * RGC_I64) as *i64
1346 let prev1: *i64 = sys_mmap(RP_P_SLOTS * RGC_I64) as *i64
1347 prev1[RP_P_BASELINE] = 1
1348 return rp_census(dom, ev, 0, tot1, wk1, prev1, d)
1349 }
1350 let rung: *u8 = argv[3] as *u8
1351 let links: *i64 = sys_mmap(RGC_LINKS * RGC_I64) as *i64
1352 let f: *i64 = sys_mmap(RGC_F_SLOTS * RGC_I64) as *i64
1353 var declared: i64 = rp_eval(rung, ev, links, f, d)
1354 if declared == 0 {
1355 gw("RUNGCLOSE domain=" as *u8); gw(dom); gw(" rung=" as *u8); gw(rung); gw(" stage=NORUNG (the board declares no such rung) verdict=NOT-PRODUCTION\n" as *u8)
1356 return RP_EXIT_NOT
1357 }
1358 // A rung id DECLARED MORE THAN ONCE makes every row that names it ambiguous (measured 2026-09-17: two seats appended
1359 // their own EC64 to one board minutes apart). check SAYS so; journal REFUSES until one of them is renumbered.
1360 if f[RGC_F_DECLS] > 1 {
1361 gw("DUPLICATE-RUNG-ID domain=" as *u8); gw(dom); gw(" rung=" as *u8); gw(rung); gw(" declared=" as *u8); gn(f[RGC_F_DECLS])
1362 gw(": two rung rows share this id, so every journal row that names it is ambiguous -- renumber the later one\n" as *u8)
1363 if isjournal == 1 { gw(" verdict=REFUSED\n" as *u8); return RP_EXIT_NOT }
1364 }
1365 if isretired == 1 {
1366 gw("SIGN-OFF RETIRED 2026-09-18 (operator: completion needs no manual sign-off): the stage below is MEASURED from the gate roster, the surface census and the clock's run rows; nothing was written\n" as *u8)
1367 }
1368 var jok: i64 = 0
1369 if isjournal == 1 {
1370 jok = rp_journal(rung, argv[4] as *u8, argv[5] as *u8, dom, ev)
1371 if jok == 1 {
1372 rp_gather_board(dom, ev)
1373 declared = rp_eval(rung, ev, links, f, d)
1374 }
1375 }
1376 rp_print(dom, rung, ev, links, f, d)
1377 let x: i64 = rp_exit_for(links, d)
1378 if isjournal == 1 {
1379 if jok == 1 { gw(" verdict=JOURNALED\n" as *u8); return RP_EXIT_DONE }
1380 gw(" verdict=REFUSED\n" as *u8)
1381 return RP_EXIT_NOT
1382 }
1383 gw(" verdict=" as *u8); gw(rp_exit_word(x)); gw("\n" as *u8)
1384 return x
1385}