code wiki / (root) / nx_observatory.nx

nx_observatory.nx source

↩ module page · 211 lines · 8028 B

1// nx_observatory.nx -- external watcher for paired-cell dyads. 2// 3// Astronomical observatories watch celestial bodies from a distance 4// without touching them. Nishi observatory watches nx_dyad pairs 5// (or organism-level cell sets) from outside the cells' isolation 6// boundaries -- it READS what cells emit but never writes into them. 7// 8// Per [[feedback-cell-immune-system-ransomware-judo-ddos-by-bit]]: 9// "verify D observer received parallel telemetry without vacuole 10// bytes crossing." Observatory is the substrate primitive for that 11// "parallel telemetry without crossing." Sees both A's and B's 12// outputs side by side; computes a comparison metric; surfaces 13// divergence + agreement counts. 14// 15// Naming: nx_observer is too generic (Observer pattern in nearly 16// every framework). nx_observatory keeps biology/science-domain 17// distance and matches the "external scientific instrument" 18// semantic exactly. 19// 20// Composes: 21// nx_dyad -- the paired cells being watched 22// nx_provenance_chain -- observatory's reading is a transform; 23// chain-logged 24// nx_evict_journal -- significant divergence events logged 25// nx_xenocell -- when one cell of the dyad turns hostile, the 26// observatory catches the deviation before it 27// spreads 28// 29// V1 ships: 30// - struct NxObservatory with dyad pointer + sample ring + 31// agreement_count / divergence_count 32// - sample(): grab one reading of A and B's outputs, compute 33// diff bytes, increment agree-or-diverge counter 34// - agreement_q10(): Q10 ratio of agreements to total samples 35// - is_divergent(): predicate firing when divergence_count 36// exceeds caller-supplied threshold 37// 38// Gap list (V1 honest perf verdict): 39// - byte-equality diff is coarse (V2 supports semantic-diff 40// callbacks for structured outputs) 41// - no time-windowed metrics (V2 adds rolling window) 42// - no automatic alert escalation (caller polls) 43// 44// genealogy_id: cardinal_2026-05-17_cell_immune_judo + 45// astronomy_observatory_external_watcher 46// lineage_id: substrate_observatory_v1 47// 48// nx_safety_envelope: 49// intended_use: "External read-only watcher over paired 50// cells; no cross-cell side-channel via 51// observatory" 52// sil_target: SIL2 53// evidence: [read_only_no_writes_into_cells, 54// side_channel_isolation_explicit] 55// verdict: NOT_YET_EVALUATED 56 57import "nx_syscalls.nx" 58import "nx_tier.nx" 59import "nx_dyad.nx" 60const NX_MAGIC_1024: i64 = 1024 61 62// ===== Sealed enum: NxObservatoryVerdict ========================== 63 64const NX_OBS_OK: nx_int = 0 65const NX_OBS_ERR_BAD_DYAD: nx_int = 1 66const NX_OBS_ERR_OUT_OF_RANGE: nx_int = 2 67 68// ===== Struct: NxObservatorySample ================================ 69// 70// One reading of (A, B) outputs at a moment in time. ts_us is 71// monotonic; diff_bytes is how many byte positions differ between 72// A's output and B's output for the sampled length. 73 74struct NxObservatorySample { 75 ts_us: nx_size, 76 sample_len: nx_size, 77 diff_bytes: nx_size, 78 a_out_first_byte: nx_int, // first byte of A's output (debugging) 79 b_out_first_byte: nx_int, 80} 81 82// ===== Struct: NxObservatory ====================================== 83// 84// dyad is the paired-cell target. samples is a ring of readings; 85// capacity caller-fixed at construction. agree_count + diverge_count 86// are running totals across all samples (samples may overwrite older 87// entries; totals are monotonic). 88 89struct NxObservatory { 90 dyad: *NxDyad, 91 samples: *NxObservatorySample, 92 capacity: nx_size, 93 head: nx_size, 94 sample_count: nx_size, 95 agree_count: nx_int, 96 diverge_count: nx_int, 97} 98 99const NX_OBS_SAMPLE_BYTES: nx_size = 40 100 101// ===== nx_observatory_new ========================================= 102 103func nx_observatory_new(dyad: *NxDyad, capacity: nx_size) -> *NxObservatory { 104 if (dyad as i64) == 0 { return (0 as i64) as *NxObservatory } 105 let o: *NxObservatory = (sys_mmap(48)) as *NxObservatory 106 let bytes: nx_size = capacity * NX_OBS_SAMPLE_BYTES 107 o.samples = (sys_mmap(bytes)) as *NxObservatorySample 108 o.dyad = dyad 109 o.capacity = capacity 110 o.head = 0 111 o.sample_count = 0 112 o.agree_count = 0 113 o.diverge_count = 0 114 return o 115} 116 117// ===== _obs_at ==================================================== 118 119func _obs_at(o: *NxObservatory, idx: nx_size) -> *NxObservatorySample { 120 return (o.samples as i64 + (idx as i64) * NX_OBS_SAMPLE_BYTES) as *NxObservatorySample 121} 122 123// ===== nx_observatory_sample ====================================== 124// 125// Take one reading of the dyad's outputs. sample_len bytes of each 126// cell's output are compared; diff_bytes is the count of positions 127// that differ. agree_count or diverge_count is incremented based on 128// whether diff_bytes is zero. 129 130func nx_observatory_sample(o: *NxObservatory, 131 sample_len: nx_size, 132 now_us: nx_size) -> nx_int { 133 if (o.dyad as i64) == 0 { return NX_OBS_ERR_BAD_DYAD } 134 let diff: nx_size = nx_dyad_outputs_differ(o.dyad, sample_len) 135 let slot: *NxObservatorySample = _obs_at(o, o.head) 136 slot.ts_us = now_us 137 slot.sample_len = sample_len 138 slot.diff_bytes = diff 139 if sample_len > 0 { 140 slot.a_out_first_byte = (o.dyad.cell_a_out[0] as i64) & 255 141 slot.b_out_first_byte = (o.dyad.cell_b_out[0] as i64) & 255 142 } else { 143 slot.a_out_first_byte = 0 144 slot.b_out_first_byte = 0 145 } 146 o.head = o.head + 1 147 if o.head >= o.capacity { o.head = 0 } 148 o.sample_count = o.sample_count + 1 149 if diff == 0 { 150 o.agree_count = o.agree_count + 1 151 } else { 152 o.diverge_count = o.diverge_count + 1 153 } 154 return NX_OBS_OK 155} 156 157// ===== nx_observatory_agreement_q10 ============================== 158// 159// Q10 ratio of agreements over total samples. 1024 = 100% agreement; 160// 0 = 0% agreement. Used by caller to score dyad's behavior: 161// ~1024 = A and B always agree on the input (twins; useful for 162// A/B regression testing -- code change shouldn't change 163// behavior) 164// moderate = A and B sometimes diverge (real A/B test surfacing 165// difference between two implementations) 166// ~0 = A and B always disagree (each makes different choices for 167// same input; useful for diversity-redundancy patterns) 168 169func nx_observatory_agreement_q10(o: *NxObservatory) -> nx_int { 170 let total: nx_int = o.agree_count + o.diverge_count 171 if total <= 0 { return 0 } 172 return (o.agree_count * NX_MAGIC_1024) / total 173} 174 175// ===== nx_observatory_is_divergent =============================== 176// 177// Predicate: did the dyad diverge more than threshold_q10 of its 178// samples? Used to trigger alert when "A and B should agree" but 179// are starting to differ (regression detected, or one cell drifting). 180 181func nx_observatory_is_divergent(o: *NxObservatory, threshold_q10: nx_int) -> nx_int { 182 let total: nx_int = o.agree_count + o.diverge_count 183 if total <= 0 { return 0 } 184 let divergence_q10: nx_int = (o.diverge_count * NX_MAGIC_1024) / total 185 if divergence_q10 > threshold_q10 { return 1 } 186 return 0 187} 188 189// ===== nx_observatory_sample_count =============================== 190 191func nx_observatory_sample_count(o: *NxObservatory) -> nx_size { 192 return o.sample_count 193} 194 195// ===== nx_observatory_total_diff_bytes =========================== 196// 197// Walk the live samples and sum diff_bytes across them. Useful for 198// "total drift" reports across an observation window. 199 200func nx_observatory_total_diff_bytes(o: *NxObservatory) -> nx_size { 201 var total: nx_size = 0 202 var live: nx_size = o.sample_count 203 if live > o.capacity { live = o.capacity } 204 var i: nx_size = 0 205 while i < live { 206 let s: *NxObservatorySample = _obs_at(o, i) 207 total = total + s.diff_bytes 208 i = i + 1 209 } 210 return total 211}