code wiki / _hdl_build / nx_hostop.nx

nx_hostop.nx source

↩ module page · 267 lines · 18700 B

1// nx_hostop.nx -- THE NISHI HOST OPERATOR (library). The single sovereign owner of host-facing application, in 2// COORDINATION with the publisher: the publisher STAGES + verifies a release (records it PUBLISHED in its ledger and 3// drops the bytes in its liveroot); the Host Operator RECEIVES that verified output and applies it to the live host. 4// 5// R1 -- RECEIVE CONTRACT (read-only): hostop_receive reads the publisher's ledger and counts the releases that are 6// APPLY-READY = recorded PUBLISHED AND actually present in the publisher's liveroot. It never re-publishes and never 7// invents work: a ledgered release whose bytes are NOT staged is a PHANTOM and is excluded (you cannot apply what is 8// not staged). This is the seam: host-operator CONSUMES publisher output; it does not reach into a workstream. 9// Composes the publisher's own field/path primitives (DRY, and it IS the coordination partner). license_tier: ORIGINAL 10import "nx_syscalls.nx" 11import "nx_publisher.nx" 12import "nx_pub_recover.nx" 13 14// count apply-ready releases: ledger rows with outcome==PUBLISHED whose liveroot/<dest> bytes exist. READ-ONLY. 15func hostop_receive(pub_ledger: *u8, liveroot: *u8) -> i64 { 16 let lenp: *i64 = sys_mmap(8) as *i64 17 let data: *u8 = sys_read_file(pub_ledger, lenp) 18 if (data as i64) == 0 { return 0 } 19 let n: i64 = lenp[0] 20 let dest: *u8 = sys_mmap(256); let oc: *u8 = sys_mmap(64) 21 var i: i64=0; var ls: i64=0; var ready: i64=0 22 while i < n { 23 if data[i] == (10 as u8) { 24 let line: *u8 = ((data as i64)+ls) as *u8 25 let ll: i64 = i - ls 26 pub_field(line, ll, 3, oc) // outcome field 27 if pub_streq(oc, "PUBLISHED" as *u8) == 1 { 28 pub_field(line, ll, 1, dest) 29 let livep: *u8 = sys_mmap(700); pub_join(liveroot, dest, livep) 30 if pub_exists(livep) == 1 { ready = ready + 1 } // apply-ready iff actually staged 31 } 32 ls = i + 1 33 } 34 i = i + 1 35 } 36 return ready 37} 38// is a specific dest apply-ready (publisher-verified + staged)? 1/0. (used by later apply rungs) 39func hostop_is_ready(pub_ledger: *u8, liveroot: *u8, dest: *u8) -> i64 { 40 let lenp: *i64 = sys_mmap(8) as *i64 41 let data: *u8 = sys_read_file(pub_ledger, lenp) 42 if (data as i64) == 0 { return 0 } 43 let n: i64 = lenp[0] 44 let d2: *u8 = sys_mmap(256); let oc: *u8 = sys_mmap(64) 45 var i: i64=0; var ls: i64=0; var found: i64=0 46 while i < n { 47 if data[i] == (10 as u8) { 48 let line: *u8 = ((data as i64)+ls) as *u8 49 let ll: i64 = i - ls 50 pub_field(line, ll, 3, oc); pub_field(line, ll, 1, d2) 51 if pub_streq(oc, "PUBLISHED" as *u8) == 1 { if pub_streq(d2, dest) == 1 { 52 let livep: *u8 = sys_mmap(700); pub_join(liveroot, dest, livep) 53 if pub_exists(livep) == 1 { found = 1 } 54 } } 55 ls = i + 1 56 } 57 i = i + 1 58 } 59 return found 60} 61 62// ============================================================================================================ 63// R2/R3 -- ATOMIC HOST-SWAP + HOST-ROLLBACK, NEVER-BRICK BY CONSTRUCTION (#26). The host-operator applies a received 64// release to the host-root by COMPOSING the publisher's PROVEN never-brick primitives: keep the current host file as 65// .prev (rollback target), then atomic copy->tmp->rename (a reader sees the OLD or NEW host file, never a half-write). 66// Nested host paths are mkdir-p'd. By construction every host-write is reversible (.prev) + atomic (rename) -> the 67// host can never be left half-applied or unrecoverable. (The real .240 application is operator/hosting-coordinated; 68// this is the capability, gated in a sandbox host-root.) 69// ============================================================================================================ 70func hostop_apply(src: *u8, host_root: *u8, dest: *u8) -> i64 { 71 let livep: *u8 = sys_mmap(700); pub_join(host_root, dest, livep) 72 pub_mkdirp(livep) // create nested host dirs (never-brick: additive, EEXIST-safe) 73 pub_backup_prev(livep) // keep prev = rollback target (reversible) 74 return pub_promote_atomic(src, livep) // atomic copy->tmp->rename (never half-applied) 75} 76// restore the previous host file atomically. returns 1 if rolled back, 0 if no prev (no fabricated restore). 77func hostop_host_rollback(host_root: *u8, dest: *u8) -> i64 { 78 let livep: *u8 = sys_mmap(700); pub_join(host_root, dest, livep) 79 return pub_rollback(livep) 80} 81// SAFE apply: only apply a release the publisher has verified+staged (R1 contract) AND it must verify against the 82// approved sha; then atomic host-swap. returns 1 applied, 0 not-ready/sha-mismatch (host untouched). fail-closed. 83func hostop_apply_verified(pub_ledger: *u8, liveroot: *u8, host_root: *u8, dest: *u8, expect_sha: *u8) -> i64 { 84 if hostop_is_ready(pub_ledger, liveroot, dest) == 0 { return 0 } // not publisher-verified+staged 85 let livesrc: *u8 = sys_mmap(700); pub_join(liveroot, dest, livesrc) 86 if pub_sha_match(livesrc, expect_sha) == 0 { return 0 } // integrity re-check before host write 87 return hostop_apply(livesrc, host_root, dest) 88} 89 90// ============================================================================================================ 91// R4 -- NO-DIRECT-HOST LAW. No workstream applies to the LIVE HOST except via the host-operator: the host-apply + 92// host-transport primitives (hostop_apply / nx_aw_send / nx_aw_push) are host-operator/hosting-INTERNAL. Call-aware 93// (strips // comments; matches symbol+'(' = a real call, not a mention) so it does not over-flag prose/HTML. This is 94// the host-layer twin of the publisher's no-bypass law -- the sites.elf-outage fix made mechanical. 95// ============================================================================================================ 96func hostop_forbidden_call(code: *u8, n: i64) -> i64 { 97 if pub_substr(code, n, "hostop_apply(" as *u8)==1 { return 1 } 98 if pub_substr(code, n, "nx_aw_send(" as *u8)==1 { return 1 } 99 if pub_substr(code, n, "nx_aw_push(" as *u8)==1 { return 1 } 100 return 0 101} 102func hostop_audit_file(path: *u8, allowlisted: i64) -> i64 { 103 if allowlisted == 1 { return 0 } 104 let lenp: *i64 = sys_mmap(8) as *i64 105 let data: *u8 = sys_read_file(path, lenp) 106 if (data as i64)==0 { return 0 } 107 let n: i64 = lenp[0] 108 let code: *u8 = sys_mmap(n + 16) 109 var i: i64=0; var o: i64=0; var incomment: i64=0 110 while i < n { 111 let c: i64 = data[i] & 0xff 112 if c == 10 { incomment=0; code[o]=10 as u8; o=o+1 } 113 else { 114 if incomment == 0 { 115 if c == 47 { if i+1 < n { if (data[i+1] & 0xff) == 47 { incomment = 1 } } } 116 if incomment == 0 { code[o]=c as u8; o=o+1 } 117 } 118 } 119 i = i + 1 120 } 121 return hostop_forbidden_call(code, o) 122} 123 124// ============================================================================================================ 125// R6 -- DNS-APPLY EXECUTOR. Consumes the publisher's emitted pub_dns directive (publish-endpoint domain= host= port= 126// proto=https) and produces the host-operator's APPLY command into `out` (what hosting runs to set the DNS A / proxy). 127// Validates the directive is well-formed first (fail-closed on malformed). The host-operator EXECUTES via hosting's 128// control-plane (nx_aw_hostctl) -- coordinated; this rung is the translate+validate step. returns out length, or <0. 129// ============================================================================================================ 130func hostop_dns_apply(directive_path: *u8, out: *u8, cap: i64) -> i64 { 131 let lenp: *i64 = sys_mmap(8) as *i64 132 let data: *u8 = sys_read_file(directive_path, lenp) 133 if (data as i64) == 0 { return 0 - 1 } 134 let n: i64 = lenp[0] 135 if pub_substr(data, n, "domain=" as *u8) == 0 { return 0 - 2 } // malformed -> reject (fail-closed) 136 if pub_substr(data, n, "host=" as *u8) == 0 { return 0 - 2 } 137 if pub_substr(data, n, "port=" as *u8) == 0 { return 0 - 2 } 138 if pub_substr(data, n, "proto=https" as *u8) == 0 { return 0 - 2 } 139 var o: i64 = 0 140 o = fa_cat(out, o, "nx_aw_hostctl setdns " as *u8) // the host-operator apply command (hosting runs it) 141 var i: i64 = 0 142 while i < n { if data[i]!=(10 as u8) { if data[i]!=(13 as u8) { out[o]=data[i]; o=o+1 } } i=i+1 } 143 out[o] = 0 as u8 144 return o 145} 146 147// ============================================================================================================ 148// R7 -- PROGRESSIVE HOST DELIVERY: multi-host fan-out, rolling update, blue-green -- composing the publisher's PROVEN 149// primitives onto host-roots (host_roots[] = *i64 array of host-root path pointers). Each apply is the never-brick 150// atomic host-swap (hostop_apply). All from ONE publisher-verified source (integrity checked once / per host). 151// ============================================================================================================ 152// fan out a verified release to N hosts: verify ready+sha ONCE, then atomic-apply to each host. returns #hosts applied. 153func hostop_fanout(pub_ledger: *u8, liveroot: *u8, dest: *u8, expect_sha: *u8, host_roots: *i64, nhosts: i64) -> i64 { 154 if hostop_is_ready(pub_ledger, liveroot, dest) == 0 { return 0 } 155 let livesrc: *u8 = sys_mmap(700); pub_join(liveroot, dest, livesrc) 156 if pub_sha_match(livesrc, expect_sha) == 0 { return 0 } // fail-closed: no host gets bad bytes 157 var i: i64=0; var done: i64=0 158 while i < nhosts { 159 if hostop_apply(livesrc, host_roots[i] as *u8, dest) == 1 { done = done + 1 } 160 i = i + 1 161 } 162 return done 163} 164// rolling host update: apply one host at a time; HALT at the first failure (later hosts untouched = blast-limited). 165func hostop_rolling(pub_ledger: *u8, liveroot: *u8, dest: *u8, expect_sha: *u8, host_roots: *i64, nhosts: i64) -> i64 { 166 var i: i64=0 167 while i < nhosts { 168 if hostop_apply_verified(pub_ledger, liveroot, host_roots[i] as *u8, dest, expect_sha) != 1 { return i } 169 i = i + 1 170 } 171 return nhosts 172} 173// host blue-green: two materialized host slots + atomic symlink flip (instant flip + zero-copy flip-back rollback). 174// composes the publisher's proven blue-green primitives onto the host-root. 175func hostop_blue_green_stage(host_root: *u8, dest: *u8, color: *u8, src: *u8, expect_sha: *u8) -> i64 { 176 return pub_blue_green_stage(host_root, dest, color, src, expect_sha) 177} 178func hostop_blue_green_flip(host_root: *u8, dest: *u8, color: *u8) -> i64 { 179 return pub_blue_green_flip(host_root, dest, color) 180} 181 182// ============================================================================================================ 183// R8 -- HOST STATE MANAGEMENT (Terraform/Ansible class): DRIFT detection, DRY-RUN plan, IDEMPOTENT apply. Desired 184// state = the publisher-approved sha. Composes pub_sha_match. Read-only except idempotent-apply (which only writes 185// when the host has drifted -> no needless host writes). 186// ============================================================================================================ 187// 1 if the live host differs from desired (missing OR wrong sha) = DRIFTED; 0 if it matches desired (no drift). 188func hostop_drift(host_root: *u8, dest: *u8, expect_sha: *u8) -> i64 { 189 let livep: *u8 = sys_mmap(700); pub_join(host_root, dest, livep) 190 if pub_exists(livep) == 0 { return 1 } // missing = drifted 191 if pub_sha_match(livep, expect_sha) == 1 { return 0 } // matches desired = no drift 192 return 1 // differs = drifted 193} 194// DRY-RUN: how many of N hosts WOULD change (are drifted) -- a plan, applies NOTHING. 195func hostop_dryrun(host_roots: *i64, nhosts: i64, dest: *u8, expect_sha: *u8) -> i64 { 196 var i: i64=0; var change: i64=0 197 while i < nhosts { if hostop_drift(host_roots[i] as *u8, dest, expect_sha) == 1 { change = change + 1 } i = i + 1 } 198 return change 199} 200// IDEMPOTENT apply: only write the host if it has DRIFTED; if already desired, NO-OP. returns 2=no-op (already 201// matching), 1=applied (was drifted), 0=not-ready/sha-fail. So re-applying a converged host costs zero host writes. 202func hostop_idempotent(pub_ledger: *u8, liveroot: *u8, host_root: *u8, dest: *u8, expect_sha: *u8) -> i64 { 203 if hostop_drift(host_root, dest, expect_sha) == 0 { return 2 } // already desired -> no-op 204 return hostop_apply_verified(pub_ledger, liveroot, host_root, dest, expect_sha) 205} 206 207// ============================================================================================================ 208// R9 -- HOST OBSERVABILITY: a host-apply LEDGER + history query + DORA metrics + deploy notifications, composing the 209// publisher's PROVEN ledger/audit/dora/notify organs onto a host-apply event log (append-only, content-addressed). 210// ============================================================================================================ 211// record a host-apply event (sha/dest/host/PUBLISHED/ts) to the host-apply ledger. 212func hostop_record_apply(host_ledger: *u8, sha: *u8, dest: *u8, host: *u8) -> i64 { return pub_record_ledger(host_ledger, sha, dest, host) } 213// AUDIT/history: what is applied at a host path + from which sha (latest wins). 1 + sha into out_sha, else 0. 214func hostop_history(host_ledger: *u8, dest: *u8, out_sha: *u8) -> i64 { return pub_audit_query(host_ledger, dest, out_sha) } 215// host DORA metrics from the host-apply ledger (out[] = pub_dora layout: deploys/succ/fail/cfr/span/freq/mttr/.../lead). 216func hostop_metrics(host_ledger: *u8, out: *i64) -> i64 { return pub_dora(host_ledger, out) } 217// host deploy NOTIFICATION onto a framed event channel (alert equivalent). 218func hostop_notify(notifypath: *u8, kind: *u8, dest: *u8, sha: *u8) -> i64 { return pub_notify(notifypath, kind, dest, sha) } 219 220// ============================================================================================================ 221// R10 -- WIRE THE COMPOSED-OPS (recovery + served-smoke + health-gated auto-rollback + canary) by composing the 222// PROVEN organs onto the host. pr_recover = the wedge-aware recovery decision (APM-fed); pub_smoke/pub_health_gate/ 223// pub_canary_rollout = the publisher's measured served-verify + auto-rollback + blast-limited rollout, applied to a 224// host-root served by psv_listen. (Real-host serving daemon = coordinated; gates fork psv_listen on a sandbox host.) 225// ============================================================================================================ 226// wedge-aware host recovery: scan the monitor's metrics, emit idempotent+serialized recovery requests (= pr_recover). 227func hostop_recover(metricspath: *u8, queuepath: *u8, wedge_ms: i64) -> i64 { return pr_recover(metricspath, queuepath, wedge_ms) } 228// served-smoke at the host URL: 200 + served-body hashes to the approved sha (= pub_smoke). 229func hostop_host_smoke(port: i64, urlpath: *u8, expect_sha: *u8) -> i64 { return pub_smoke(port, urlpath, expect_sha) } 230// health-gated auto-rollback: after a host apply, smoke the host URL; if it fails, roll the host back to last-good. 231func hostop_health_gate(expect_sha: *u8, host_livepath: *u8, port: i64, urlpath: *u8) -> i64 { return pub_health_gate(expect_sha, host_livepath, port, urlpath) } 232// host CANARY: stage to a canary slot, served-smoke a cohort, promote to main ONLY if healthy (else abort, main 0% blast). 233func hostop_canary(expect_sha: *u8, src: *u8, host_root: *u8, dest: *u8, urlbase: *u8, port: i64, cohort: i64) -> i64 { return pub_canary_rollout(expect_sha, src, host_root, dest, urlbase, port, cohort) } 234 235// ============================================================================================================ 236// R11 -- CONTROL-PLANE COMPOSITION (coordinate with hosting). The host-operator DECIDES + builds the nx_aw_hostctl 237// invocation and makes the health DECISION over its output; the hosting workstream's nx_aw_hostctl (machine-bound 238// vault -> sudo on the host) EXECUTES it. Consistent with hostop_dns_apply. The execution is the coordinated leg; 239// these are the host-operator's part (sandbox-gateable: command-build + decision). 240// ============================================================================================================ 241// PRE-APPLY HEALTH: the command to ask the host's health, + the DECISION over its status output (fail-closed). 242func hostop_prehealth_cmd(out: *u8) -> i64 { var o: i64=0; o=fa_cat(out, o, "nx_aw_hostctl status" as *u8); out[o]=0 as u8; return o } 243func hostop_prehealth_ok(status: *u8, n: i64) -> i64 { 244 if pub_substr(status, n, "DOWN" as *u8) == 1 { return 0 } // any service down -> NOT healthy (fail-closed: do not apply) 245 if pub_substr(status, n, "RED" as *u8) == 1 { return 0 } 246 if pub_substr(status, n, "UP" as *u8) == 1 { return 1 } // services up -> healthy -> safe to apply 247 return 0 // unknown -> not healthy (fail-closed) 248} 249// DAEMON SUPERVISION: build the nx_aw_hostctl command for an action (startsite/reconcile) on a daemon (hosting runs it). 250func hostop_supervise_cmd(action: *u8, daemon: *u8, out: *u8) -> i64 { 251 var o: i64=0; o=fa_cat(out, o, "nx_aw_hostctl " as *u8); o=fa_cat(out, o, action); out[o]=32 as u8; o=o+1; o=fa_cat(out, o, daemon); out[o]=0 as u8; return o 252} 253 254// ============================================================================================================ 255// R12 -- WATCHDOG the publisher daemon (the full supervised lifecycle, both roles tied). Reads the daemon's heartbeat 256// (pub_daemon_hb_age): if it is STALE (hung past hang_limit) or MISSING (-1 = not running), build the supervisor 257// restart/launch command (hosting executes); else healthy, no action. This is the host-operator OWNING the daemon's 258// liveness = the wedge-aware watchdog from the daemon research (dmn_d_*), applied to the publisher daemon. 259// returns 1 (restart needed, cmd in out) or 0 (healthy). now = sys_now_realtime_sec(). 260// ============================================================================================================ 261func hostop_watchdog(hbpath: *u8, daemon: *u8, hang_limit_s: i64, now: i64, out: *u8) -> i64 { 262 let age: i64 = pub_daemon_hb_age(hbpath, now) 263 if age < 0 { hostop_supervise_cmd("startsite" as *u8, daemon, out); return 1 } // no heartbeat -> not running -> launch 264 if age > hang_limit_s { hostop_supervise_cmd("startsite" as *u8, daemon, out); return 1 } // stale -> hung -> restart 265 out[0] = 0 as u8 266 return 0 // healthy -> no action 267}