nx_crispr.nx source
↩ module page · 223 lines · 8134 B
1// nx_crispr.nx -- adaptive signature memory (Tier-1 immune).
2//
3// Biology: CRISPR-Cas is the bacterial adaptive immune system. When
4// a virus infects, bacteria capture a fragment of viral DNA as a
5// "spacer" in their CRISPR array; on next encounter, Cas enzymes
6// match the spacer and cleave the virus. Substrate equivalent: when
7// nx_pamp detects a hit, nx_crispr remembers its content-addressed
8// signature; on next encounter the lookup is O(1) and the response
9// can fire WITHOUT re-running pamp's expensive scanners.
10//
11// THIS COMPLETES THE LEARN-FROM-EXPERIENCE LOOP. nx_pamp is the
12// pattern recognition; nx_crispr is the memory. Together they
13// implement adaptive innate immunity. Per [[feedback-unified-immune-
14// architecture-three-tier]] the signature DB grows across encounters
15// and (V2) shares via peer mesh so every Nishi cell benefits from
16// every Nishi cell's incident history.
17//
18// Composes:
19// nx_pamp -- supplier of new signatures to remember
20// nx_xenocell -- the source of confirmed-hostile events
21// nx_decoy -- decoy hits become CRISPR entries since they prove
22// someone read the decoy
23// nx_evict_journal -- pre-existing forensic substrate; CRISPR
24// complements (pamp_journal is event log,
25// crispr is THE SIGNATURE INDEX)
26//
27// V1 ships a fixed-capacity ring of spacer entries. V2 makes it
28// peer-sharable (content-addressed merkle tree) so the community
29// shares intrusion intelligence without leaking specifics.
30//
31// Gap list (V1 honest perf verdict):
32// - linear-scan lookup; V2 builds hash index for O(1)
33// - no automatic expiration of stale spacers
34// - no peer-mesh federation
35// - no signature-class grouping (every spacer treated independently)
36//
37// genealogy_id: cardinal_2026-05-19_tier_1_innate_immune_microbial +
38// biology_CRISPR_Cas_adaptive_immunity
39// lineage_id: substrate_crispr_v1
40//
41// nx_safety_envelope:
42// intended_use: "Adaptive memory of confirmed attacker
43// signatures; cheap second-encounter matching"
44// sil_target: SIL2
45// evidence: [append_only_invariant,
46// content_addressed_lookup]
47// verdict: NOT_YET_EVALUATED
48
49import "nx_syscalls.nx"
50import "nx_tier.nx"
51import "nx_pamp.nx"
52
53// ===== Sealed enum: NxCrisprVerdict ===============================
54
55const NX_CRISPR_OK: nx_int = 0
56const NX_CRISPR_ERR_FULL: nx_int = 1
57const NX_CRISPR_HIT: nx_int = 2 // signature matched
58const NX_CRISPR_MISS: nx_int = 3 // signature not in DB
59
60// ===== Struct: NxCrisprSpacer =====================================
61//
62// One row per confirmed attacker signature. kind aliases NX_PAMP_*
63// constants (or caller's own taxonomy for non-PAMP-detected events).
64// content_hash is BLAKE3 (or equivalent) of the offending byte
65// region. first_seen_us + last_seen_us bracket the attack window;
66// hit_count tracks recurrence.
67
68struct NxCrisprSpacer {
69 kind: nx_int,
70 content_hash: nx_size,
71 first_seen_us: nx_size,
72 last_seen_us: nx_size,
73 hit_count: nx_int,
74}
75
76// ===== Struct: NxCrisprArray ======================================
77//
78// Bacterial CRISPR array = ordered list of spacers. Append-only ring;
79// when at capacity, oldest entries roll out (caller queries count vs
80// capacity to detect history loss).
81
82struct NxCrisprArray {
83 spacers: *NxCrisprSpacer,
84 capacity: nx_size,
85 head: nx_size,
86 count: nx_size,
87}
88
89const NX_CRISPR_SPACER_BYTES: nx_size = 40
90const NX_CRISPR_DEFAULT_CAPACITY: nx_size = 256
91
92// ===== Constructor ===============================================
93
94func nx_crispr_new(capacity: nx_size) -> *NxCrisprArray {
95 let a: *NxCrisprArray = (sys_mmap(32)) as *NxCrisprArray
96 let bytes: nx_size = capacity * NX_CRISPR_SPACER_BYTES
97 a.spacers = (sys_mmap(bytes)) as *NxCrisprSpacer
98 a.capacity = capacity
99 a.head = 0
100 a.count = 0
101 return a
102}
103
104// ===== _crispr_at ================================================
105
106func _crispr_at(a: *NxCrisprArray, idx: nx_size) -> *NxCrisprSpacer {
107 return (a.spacers as i64 + (idx as i64) * NX_CRISPR_SPACER_BYTES) as *NxCrisprSpacer
108}
109
110// ===== nx_crispr_match ===========================================
111//
112// Linear-scan check: is (kind, content_hash) already in the array?
113// Returns NX_CRISPR_HIT and (via out_idx) the spacer index if found;
114// NX_CRISPR_MISS otherwise. Caller can then call nx_crispr_touch to
115// update last_seen + hit_count.
116
117func nx_crispr_match(a: *NxCrisprArray,
118 kind: nx_int,
119 content_hash: nx_size,
120 out_idx: *i64) -> nx_int {
121 var live: nx_size = a.count
122 if live > a.capacity { live = a.capacity }
123 var i: nx_size = 0
124 while i < live {
125 let s: *NxCrisprSpacer = _crispr_at(a, i)
126 if s.kind == kind {
127 if s.content_hash == content_hash {
128 out_idx[0] = i as i64
129 return NX_CRISPR_HIT
130 }
131 }
132 i = i + 1
133 }
134 out_idx[0] = -1
135 return NX_CRISPR_MISS
136}
137
138// ===== nx_crispr_remember ========================================
139//
140// Append a new spacer (first-time observation). If the signature
141// already exists, this is a no-op; caller should have checked with
142// nx_crispr_match first. We re-check to keep idempotency.
143
144func nx_crispr_remember(a: *NxCrisprArray,
145 kind: nx_int,
146 content_hash: nx_size,
147 now_us: nx_size) -> nx_int {
148 if nx_pamp_kind_is_valid(kind) == 0 {
149 // allow non-pamp kinds too (caller's taxonomy may extend)
150 }
151 let probe: *i64 = (sys_mmap(8)) as *i64
152 let m: nx_int = nx_crispr_match(a, kind, content_hash, probe)
153 if m == NX_CRISPR_HIT {
154 // Already known; touch instead.
155 let idx: nx_size = probe[0] as nx_size
156 let s: *NxCrisprSpacer = _crispr_at(a, idx)
157 s.last_seen_us = now_us
158 s.hit_count = s.hit_count + 1
159 return NX_CRISPR_OK
160 }
161 let slot: *NxCrisprSpacer = _crispr_at(a, a.head)
162 slot.kind = kind
163 slot.content_hash = content_hash
164 slot.first_seen_us = now_us
165 slot.last_seen_us = now_us
166 slot.hit_count = 1
167 a.head = a.head + 1
168 if a.head >= a.capacity { a.head = 0 }
169 a.count = a.count + 1
170 return NX_CRISPR_OK
171}
172
173// ===== nx_crispr_touch ===========================================
174//
175// Caller already knows the spacer index (from a prior match). Update
176// last_seen and increment hit_count. Used in the hot path: pamp fires
177// -> crispr_match returns HIT -> crispr_touch updates -> response
178// fires using the cached signature.
179
180func nx_crispr_touch(a: *NxCrisprArray, idx: nx_size, now_us: nx_size) -> nx_int {
181 if idx >= a.capacity { return NX_CRISPR_OK }
182 let s: *NxCrisprSpacer = _crispr_at(a, idx)
183 s.last_seen_us = now_us
184 s.hit_count = s.hit_count + 1
185 return NX_CRISPR_OK
186}
187
188// ===== nx_crispr_count_kind ======================================
189//
190// How many distinct signatures of a given kind are remembered? Used
191// for trend analysis: "homoglyph attacks remembered = 247" suggests
192// active campaign.
193
194func nx_crispr_count_kind(a: *NxCrisprArray, kind: nx_int) -> nx_int {
195 var hits: nx_int = 0
196 var live: nx_size = a.count
197 if live > a.capacity { live = a.capacity }
198 var i: nx_size = 0
199 while i < live {
200 let s: *NxCrisprSpacer = _crispr_at(a, i)
201 if s.kind == kind { hits = hits + 1 }
202 i = i + 1
203 }
204 return hits
205}
206
207// ===== nx_crispr_total_hits ======================================
208//
209// Sum of hit_count across all spacers. Indicator of how often the
210// substrate has matched repeats (high = adaptive immunity is working).
211
212func nx_crispr_total_hits(a: *NxCrisprArray) -> nx_int {
213 var sum: nx_int = 0
214 var live: nx_size = a.count
215 if live > a.capacity { live = a.capacity }
216 var i: nx_size = 0
217 while i < live {
218 let s: *NxCrisprSpacer = _crispr_at(a, i)
219 sum = sum + s.hit_count
220 i = i + 1
221 }
222 return sum
223}