code wiki / (root) / nx_budget.nx

nx_budget.nx source

↩ module page · 217 lines · 8540 B

1// nx_budget.nx -- declared resource ceiling per cell. 2// 3// Per cardinal: cooperative-resource-arbitration. A cell declares its 4// hard ceiling at instantiation; allocation-beyond-budget returns 5// denial, never crashes the host. This is the structural EXCEED-axis 6// vs Docker / WSL2 / containerd silent OOM-kill: the host OS-level 7// OOM-killer cannot reach Nishi cells because cells refuse to ask for 8// more than they declared. 9// 10// Composes: 11// nx_attention_class -- foreground class can preempt background 12// budget on contention via nx_yield 13// nx_evict_journal -- every denial / forced release is logged 14// nx_homeostasis -- when used > ceiling * threshold, migration 15// signal fires (nx_tropism resolves target) 16// nx_tier -- buffer sizes (NX_BUF_*) inform sane defaults 17// across NX_TIER_MCU..NX_TIER_HPC 18// 19// V1 ships five resource kinds; further kinds (GPU compute units, 20// FPGA cells, peer-link bandwidth) are queued. 21// 22// Gap list (V1 honest perf verdict): 23// - no atomic CAS on used counters (single-threaded cell assumption) 24// - no time-windowed rate limiting (net_bps is current-instant only) 25// - no nested sub-budgets (one flat ceiling per cell) 26// - no serialization to disk (in-memory only this phase) 27// 28// genealogy_id: nishi_cardinal_2026-05-17_cooperative_resource_arbitration 29// lineage_id: substrate_budget_v1 30// 31// nx_safety_envelope: 32// intended_use: "Per-cell resource ceiling enforcement; 33// prevents host OOM by denying over-budget asks" 34// sil_target: SIL2 35// asil_target: QM 36// dal_target: DAL C 37// evidence: [denial_before_alloc, no_silent_overshoot, 38// used_le_max_invariant] 39// hazard_register: [bug-tape-budget-counter-overflow, 40// bug-tape-release-without-prior-request] 41// residual_risk: "Host kernel may still OOM if cell count 42// exceeds budget aggregate; nx_homeostasis 43// catches this at the meta-budget layer." 44// verdict: NOT_YET_EVALUATED 45 46import "nx_syscalls.nx" 47import "nx_tier.nx" 48 49// ===== Sealed enum: NxResourceKind ================================ 50 51const NX_RES_RAM: nx_int = 0 52const NX_RES_VRAM: nx_int = 1 53const NX_RES_CPU: nx_int = 2 // cpu time, microseconds per tick 54const NX_RES_DISK: nx_int = 3 // disk bytes written 55const NX_RES_NET: nx_int = 4 // network bytes per second 56const NX_RES_N_KINDS: nx_int = 5 57 58func nx_res_kind_is_valid(k: nx_int) -> nx_int { 59 if k < 0 { return 0 } 60 if k >= NX_RES_N_KINDS { return 0 } 61 return 1 62} 63 64// ===== Sealed enum: NxBudgetVerdict ============================== 65 66const NX_BUDGET_OK: nx_int = 0 67const NX_BUDGET_ERR_BAD_KIND: nx_int = 1 68const NX_BUDGET_ERR_OVER: nx_int = 2 // would exceed ceiling 69const NX_BUDGET_ERR_UNDERFLOW: nx_int = 3 // release without request 70 71// ===== Struct: NxBudget ========================================== 72// 73// One ceiling + one usage counter per resource kind. cell_id is the 74// stable identifier the eviction journal and homeostasis logs join on. 75// All counters are nx_size (pointer-width) so they scale across tiers 76// without overflow at NX_TIER_HPC scale. 77 78struct NxBudget { 79 cell_id: nx_int, 80 ram_max: nx_size, 81 ram_used: nx_size, 82 vram_max: nx_size, 83 vram_used: nx_size, 84 cpu_us_max: nx_size, 85 cpu_us_used: nx_size, 86 disk_max: nx_size, 87 disk_used: nx_size, 88 net_bps_max: nx_size, 89 net_bps_used: nx_size, 90} 91 92// ===== Constructor: allocate + zero ============================== 93 94func nx_budget_new(cell_id: nx_int, 95 ram_max: nx_size, 96 vram_max: nx_size, 97 cpu_us_max: nx_size, 98 disk_max: nx_size, 99 net_bps_max: nx_size) -> *NxBudget { 100 let b: *NxBudget = (sys_mmap(88)) as *NxBudget 101 b.cell_id = cell_id 102 b.ram_max = ram_max 103 b.ram_used = 0 104 b.vram_max = vram_max 105 b.vram_used = 0 106 b.cpu_us_max = cpu_us_max 107 b.cpu_us_used = 0 108 b.disk_max = disk_max 109 b.disk_used = 0 110 b.net_bps_max = net_bps_max 111 b.net_bps_used = 0 112 return b 113} 114 115// ===== nx_budget_request ========================================== 116// 117// Returns NX_BUDGET_OK and increments used counter when grant fits. 118// Returns NX_BUDGET_ERR_OVER and leaves state untouched when grant 119// would exceed ceiling. THE CALLER MUST NOT ALLOCATE on OVER -- this 120// is the structural OOM prevention point. 121 122func nx_budget_request(b: *NxBudget, kind: nx_int, want: nx_size) -> nx_int { 123 if nx_res_kind_is_valid(kind) == 0 { return NX_BUDGET_ERR_BAD_KIND } 124 if kind == NX_RES_RAM { 125 if b.ram_used + want > b.ram_max { return NX_BUDGET_ERR_OVER } 126 b.ram_used = b.ram_used + want 127 return NX_BUDGET_OK 128 } 129 if kind == NX_RES_VRAM { 130 if b.vram_used + want > b.vram_max { return NX_BUDGET_ERR_OVER } 131 b.vram_used = b.vram_used + want 132 return NX_BUDGET_OK 133 } 134 if kind == NX_RES_CPU { 135 if b.cpu_us_used + want > b.cpu_us_max { return NX_BUDGET_ERR_OVER } 136 b.cpu_us_used = b.cpu_us_used + want 137 return NX_BUDGET_OK 138 } 139 if kind == NX_RES_DISK { 140 if b.disk_used + want > b.disk_max { return NX_BUDGET_ERR_OVER } 141 b.disk_used = b.disk_used + want 142 return NX_BUDGET_OK 143 } 144 if kind == NX_RES_NET { 145 if b.net_bps_used + want > b.net_bps_max { return NX_BUDGET_ERR_OVER } 146 b.net_bps_used = b.net_bps_used + want 147 return NX_BUDGET_OK 148 } 149 return NX_BUDGET_ERR_BAD_KIND 150} 151 152// ===== nx_budget_release ========================================== 153// 154// Decrements used counter. Underflow (release without prior request) 155// is reported as NX_BUDGET_ERR_UNDERFLOW; counter remains at 0 so the 156// invariant used >= 0 holds even when callers misbehave. 157 158func nx_budget_release(b: *NxBudget, kind: nx_int, n: nx_size) -> nx_int { 159 if nx_res_kind_is_valid(kind) == 0 { return NX_BUDGET_ERR_BAD_KIND } 160 if kind == NX_RES_RAM { 161 if n > b.ram_used { b.ram_used = 0; return NX_BUDGET_ERR_UNDERFLOW } 162 b.ram_used = b.ram_used - n 163 return NX_BUDGET_OK 164 } 165 if kind == NX_RES_VRAM { 166 if n > b.vram_used { b.vram_used = 0; return NX_BUDGET_ERR_UNDERFLOW } 167 b.vram_used = b.vram_used - n 168 return NX_BUDGET_OK 169 } 170 if kind == NX_RES_CPU { 171 if n > b.cpu_us_used { b.cpu_us_used = 0; return NX_BUDGET_ERR_UNDERFLOW } 172 b.cpu_us_used = b.cpu_us_used - n 173 return NX_BUDGET_OK 174 } 175 if kind == NX_RES_DISK { 176 if n > b.disk_used { b.disk_used = 0; return NX_BUDGET_ERR_UNDERFLOW } 177 b.disk_used = b.disk_used - n 178 return NX_BUDGET_OK 179 } 180 if kind == NX_RES_NET { 181 if n > b.net_bps_used { b.net_bps_used = 0; return NX_BUDGET_ERR_UNDERFLOW } 182 b.net_bps_used = b.net_bps_used - n 183 return NX_BUDGET_OK 184 } 185 return NX_BUDGET_ERR_BAD_KIND 186} 187 188// ===== nx_budget_remaining ======================================== 189// 190// Reports headroom on the given kind. Useful for proactive yield 191// decisions by callers that want to back off before hitting the wall. 192 193func nx_budget_remaining(b: *NxBudget, kind: nx_int) -> nx_size { 194 if kind == NX_RES_RAM { return b.ram_max - b.ram_used } 195 if kind == NX_RES_VRAM { return b.vram_max - b.vram_used } 196 if kind == NX_RES_CPU { return b.cpu_us_max - b.cpu_us_used } 197 if kind == NX_RES_DISK { return b.disk_max - b.disk_used } 198 if kind == NX_RES_NET { return b.net_bps_max - b.net_bps_used } 199 return 0 200} 201 202// ===== nx_budget_pressure_q10 ===================================== 203// 204// Returns used/max in Q10 (0=idle, 1024=at ceiling). Homeostasis 205// queries this; >= 921 (90%) is the canonical migration trigger. 206 207func nx_budget_pressure_q10(b: *NxBudget, kind: nx_int) -> nx_int { 208 var max: nx_size = 0 209 var used: nx_size = 0 210 if kind == NX_RES_RAM { max = b.ram_max; used = b.ram_used } 211 if kind == NX_RES_VRAM { max = b.vram_max; used = b.vram_used } 212 if kind == NX_RES_CPU { max = b.cpu_us_max; used = b.cpu_us_used } 213 if kind == NX_RES_DISK { max = b.disk_max; used = b.disk_used } 214 if kind == NX_RES_NET { max = b.net_bps_max; used = b.net_bps_used } 215 if max <= 0 { return 0 } 216 return ((used as i64) * 1024) / (max as i64) 217}