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}