code wiki / (root) / nx_aerobic.nx

nx_aerobic.nx source

↩ module page · 175 lines · 6050 B

1// nx_aerobic.nx -- clean decomposition pile (regenerative metaphor). 2// 3// Per [[feedback-regenerative-stewardship-doctrine-captain-moroni]]: 4// "aerobic composting decomposes cleanly with oxygen + transparency; 5// anaerobic produces methane + smells." Substrate equivalent: when 6// stale data / expired cells / unused capabilities need to be 7// retired, do it AEROBICALLY -- with proper journal entries, content- 8// addressed receipts, operator visibility. Never silent removal. 9// 10// CRITICAL DISCIPLINE: this is the OPPOSITE of nx_lysosome. Lysosome 11// is for FOREIGN material that should be recycled at speed (cleanup 12// queue). Aerobic is for SUBSTRATE-SELF material that has aged out 13// and needs to be released with stewardship. 14// 15// Composes: 16// nx_evict_journal -- every decomposition logged 17// nx_provenance_chain -- each decomposition is a transform 18// nx_chromatin -- germline preserved before aerobic release 19// (so future cells can re-derive) 20// nx_microbiome -- stagnant microbiome (low diversity) + 21// aerobic compost = renewal signal 22// 23// V1 ships: 24// - struct NxAerobicPile of decomposable items 25// - add (mark for release with reason) 26// - check_stagnant (no add/drain in window = anaerobic risk) 27// - drain (caller frees + receives report) 28// - emit_compost_report (summary of what decomposed + when) 29 30import "nx_syscalls.nx" 31import "nx_tier.nx" 32 33// ===== Sealed enum: NxAerobicReason =============================== 34 35const NX_AR_EXPIRED: nx_int = 0 // cell/data aged out 36const NX_AR_SUPERSEDED: nx_int = 1 // newer version exists 37const NX_AR_USER_REQUESTED: nx_int = 2 // operator marked for release 38const NX_AR_UNUSED_FOR_WINDOW: nx_int = 3 // no touches in N seconds 39const NX_AR_VERSION_RETIRED: nx_int = 4 // substrate ABI retired 40const NX_AR_N_REASONS: nx_int = 5 41 42const NX_AE_OK: nx_int = 0 43const NX_AE_ERR_FULL: nx_int = 1 44const NX_AE_ERR_EMPTY: nx_int = 2 45const NX_AE_ERR_BAD_REASON: nx_int = 3 46 47// ===== Struct: NxAerobicItem ====================================== 48 49struct NxAerobicItem { 50 item_id: nx_int, 51 content_hash: nx_size, 52 added_us: nx_size, 53 reason: nx_int, 54 drained: nx_int, 55 drained_us: nx_size, 56} 57 58struct NxAerobicPile { 59 items: *NxAerobicItem, 60 capacity: nx_size, 61 head: nx_size, // next slot to write 62 count: nx_size, 63 last_activity_us: nx_size, 64 total_decomposed: nx_int, 65} 66 67const NX_AE_ITEM_BYTES: nx_size = 48 68const NX_AE_STAGNANT_WINDOW_US: nx_size = 86400000000 // 24h default 69 70func nx_ar_is_valid(r: nx_int) -> nx_int { 71 if r < 0 { return 0 } 72 if r >= NX_AR_N_REASONS { return 0 } 73 return 1 74} 75 76func nx_aerobic_pile_new(capacity: nx_size, now_us: nx_size) -> *NxAerobicPile { 77 let p: *NxAerobicPile = (sys_mmap(48)) as *NxAerobicPile 78 let bytes: nx_size = capacity * NX_AE_ITEM_BYTES 79 p.items = (sys_mmap(bytes)) as *NxAerobicItem 80 p.capacity = capacity 81 p.head = 0 82 p.count = 0 83 p.last_activity_us = now_us 84 p.total_decomposed = 0 85 return p 86} 87 88func _ae_at(p: *NxAerobicPile, idx: nx_size) -> *NxAerobicItem { 89 return (p.items as i64 + (idx as i64) * NX_AE_ITEM_BYTES) as *NxAerobicItem 90} 91 92func nx_aerobic_add(p: *NxAerobicPile, 93 item_id: nx_int, 94 content_hash: nx_size, 95 reason: nx_int, 96 now_us: nx_size) -> nx_int { 97 if nx_ar_is_valid(reason) == 0 { return NX_AE_ERR_BAD_REASON } 98 if p.count >= p.capacity { return NX_AE_ERR_FULL } 99 let it: *NxAerobicItem = _ae_at(p, p.head) 100 it.item_id = item_id 101 it.content_hash = content_hash 102 it.added_us = now_us 103 it.reason = reason 104 it.drained = 0 105 it.drained_us = 0 106 p.head = p.head + 1 107 if p.head >= p.capacity { p.head = 0 } 108 p.count = p.count + 1 109 p.last_activity_us = now_us 110 return NX_AE_OK 111} 112 113// ===== nx_aerobic_drain_one ======================================= 114// 115// Mark the oldest un-drained item as drained and return its id + 116// content_hash (via out pointers). Caller is responsible for the 117// actual underlying free (vacuole delete, brane revoke, etc.). 118 119func nx_aerobic_drain_one(p: *NxAerobicPile, 120 out_id: *i64, 121 out_hash: *i64, 122 now_us: nx_size) -> nx_int { 123 var i: nx_size = 0 124 while i < p.capacity { 125 let it: *NxAerobicItem = _ae_at(p, i) 126 if it.item_id != 0 { 127 if it.drained == 0 { 128 it.drained = 1 129 it.drained_us = now_us 130 out_id[0] = it.item_id as i64 131 out_hash[0] = it.content_hash as i64 132 p.total_decomposed = p.total_decomposed + 1 133 p.last_activity_us = now_us 134 return NX_AE_OK 135 } 136 } 137 i = i + 1 138 } 139 return NX_AE_ERR_EMPTY 140} 141 142// ===== nx_aerobic_is_stagnant ==================================== 143// 144// Predicate: no activity (add or drain) within stagnant_window_us. 145// A pile that's stagnant means decomposition has stopped -- the 146// substrate is failing its stewardship discipline. Operator should 147// investigate. 148 149func nx_aerobic_is_stagnant(p: *NxAerobicPile, now_us: nx_size) -> nx_int { 150 if now_us < p.last_activity_us { return 0 } 151 let elapsed: nx_size = now_us - p.last_activity_us 152 if elapsed > NX_AE_STAGNANT_WINDOW_US { return 1 } 153 return 0 154} 155 156func nx_aerobic_count(p: *NxAerobicPile) -> nx_size { 157 return p.count 158} 159 160func nx_aerobic_total_decomposed(p: *NxAerobicPile) -> nx_int { 161 return p.total_decomposed 162} 163 164func nx_aerobic_count_by_reason(p: *NxAerobicPile, reason: nx_int) -> nx_int { 165 var hits: nx_int = 0 166 var i: nx_size = 0 167 while i < p.capacity { 168 let it: *NxAerobicItem = _ae_at(p, i) 169 if it.item_id != 0 { 170 if it.reason == reason { hits = hits + 1 } 171 } 172 i = i + 1 173 } 174 return hits 175}