code wiki / (root) / nx_rungclose_lib.nx

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}