nx_fragility_analysis.nx source
↩ module page · 249 lines · 11462 B
1// nx_fragility_analysis.nx -- generic dependency-graph fragility primitive.
2//
3// license_tier: PUBLIC_NISHI_SUBSTRATE
4// genealogy_id: taleb_2012_antifragile_concept_inverse +
5// perrow_1984_normal_accidents_tightly_coupled_systems +
6// thwaites_2009_toaster_project_dependency_invisibility +
7// nishi_pillar_9_supply_chain_redundancy_2026 +
8// nishi_replenisher_not_consumer_cardinal_2026
9//
10// The cross-cutting "where does this system break" primitive. Given
11// a dependency graph + per-node metadata, returns the fragility
12// verdict + the named breakage points. Used by:
13//
14// - Real life: Pillar 9 supply-chain redundancy
15// (Potato Cell BOM: which components are SINGLE_SOURCE_HIGH?)
16// - In-game: Thwaites Toaster lesson made interactive
17// (player tried to build X; substrate enumerates the 47 hidden
18// deps + the 5 unsubstitutables)
19// - Both: the dependency tree IS the curriculum per
20// [[feedback-replenisher-not-consumer-substrate-shared-with-game]]
21//
22// ===== The fragility taxonomy =====================================
23//
24// Composes against:
25// - nx_manufacturing_posture.NX_SUPPLY_RISK_* (per-component risk)
26// - nx_evidence_tier (knowledge-graph fragility for claims)
27// - nx_grower_skill_tier (player competency graph)
28//
29// Domain-agnostic: applies to BOMs, supply chains, recipes, skill
30// prerequisites, biological food webs, code dependency graphs, etc.
31
32// nx_safety_envelope:
33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
34// sil_target: SIL1
35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
36// verdict: NOT_YET_EVALUATED
37
38import "nx_syscalls.nx"
39
40// ===== FragilityVerdict sealed enum ===============================
41
42const NX_FRAG_NEGLIGIBLE: i64 = 1 // no SINGLE_SOURCE_HIGH; redundancy >= 2 per critical node
43const NX_FRAG_MODERATE: i64 = 2 // some SINGLE_SOURCE_LOW; tolerable
44const NX_FRAG_HIGH: i64 = 3 // SINGLE_SOURCE_HIGH on non-critical OR multiple SINGLE_SOURCE_LOW on critical path
45const NX_FRAG_CRITICAL: i64 = 4 // SINGLE_SOURCE_HIGH on critical path; whole system at risk
46const NX_FRAG_UNKNOWN: i64 = 5
47
48func nx_fragility_verdict_name(v: i64) -> *u8 {
49 if v == NX_FRAG_NEGLIGIBLE { return "NEGLIGIBLE" }
50 if v == NX_FRAG_MODERATE { return "MODERATE" }
51 if v == NX_FRAG_HIGH { return "HIGH" }
52 if v == NX_FRAG_CRITICAL { return "CRITICAL" }
53 if v == NX_FRAG_UNKNOWN { return "UNKNOWN" }
54 return "INVALID"
55}
56
57// ===== Fragility kind sealed enum (what KIND of break is it?) =====
58
59const NX_FRAG_KIND_SINGLE_SOURCE: i64 = 1 // one supplier; can rug-pull
60const NX_FRAG_KIND_SINGLE_POINT_OF_FAIL: i64 = 2 // cut-vertex in dependency graph
61const NX_FRAG_KIND_HIDDEN_EXTERNALITY: i64 = 3 // cost paid by another part of the graph
62const NX_FRAG_KIND_MISSING_COMPETENCY: i64 = 4 // player/operator lacks required skill
63const NX_FRAG_KIND_TIGHT_COUPLING: i64 = 5 // Perrow 1984; cascading failure risk
64const NX_FRAG_KIND_TIME_DEGRADATION: i64 = 6 // component decays before use (seed viability!)
65const NX_FRAG_KIND_KNOWLEDGE_OPACITY: i64 = 7 // dependency exists but is hidden from operator
66
67func nx_fragility_kind_name(k: i64) -> *u8 {
68 if k == NX_FRAG_KIND_SINGLE_SOURCE { return "SINGLE_SOURCE" }
69 if k == NX_FRAG_KIND_SINGLE_POINT_OF_FAIL { return "SINGLE_POINT_OF_FAIL" }
70 if k == NX_FRAG_KIND_HIDDEN_EXTERNALITY { return "HIDDEN_EXTERNALITY" }
71 if k == NX_FRAG_KIND_MISSING_COMPETENCY { return "MISSING_COMPETENCY" }
72 if k == NX_FRAG_KIND_TIGHT_COUPLING { return "TIGHT_COUPLING" }
73 if k == NX_FRAG_KIND_TIME_DEGRADATION { return "TIME_DEGRADATION" }
74 if k == NX_FRAG_KIND_KNOWLEDGE_OPACITY { return "KNOWLEDGE_OPACITY" }
75 return "UNKNOWN"
76}
77
78// ===== Node metadata struct =======================================
79//
80// One row per dependency-graph node.
81
82struct FragilityNode {
83 node_id: i64,
84 node_name_ptr: *u8,
85 is_critical_path: i64, // 1 if removal breaks the goal
86 supply_risk: i64, // per nx_manufacturing_posture.NX_SUPPLY_RISK_*
87 alternates_count: i64, // number of viable substitutes
88 lead_time_days: i64,
89 externality_score_q10: i64, // hidden cost (env, social, labor) Q10
90 competency_required: i64, // skill-tier needed (per nx_grower_skill_tier)
91 time_to_decay_days: i64, // 0 if N/A; else half-life
92 knowledge_opacity: i64, // 0=transparent, 1=hidden, 2=fully opaque
93}
94
95const NX_FRAGILITY_NODE_BYTES: i64 = 80 // 10 fields * 8 bytes
96
97func nx_fragility_node_new(node_id: i64, node_name_ptr: *u8) -> *FragilityNode {
98 let raw: *u8 = sys_mmap(NX_FRAGILITY_NODE_BYTES)
99 let n: *FragilityNode = raw as *FragilityNode
100 n.node_id = node_id
101 n.node_name_ptr = node_name_ptr
102 n.is_critical_path = 0
103 n.supply_risk = 4 // MULTI_SOURCE default
104 n.alternates_count = 1
105 n.lead_time_days = 0
106 n.externality_score_q10 = 0
107 n.competency_required = 1 // APPRENTICE default
108 n.time_to_decay_days = 0
109 n.knowledge_opacity = 0
110 return n
111}
112
113// ===== Per-node fragility classifier ==============================
114//
115// Returns the FragilityVerdict for a single node given its metadata.
116// Composed across the whole graph by the dispatcher below.
117
118func nx_fragility_classify_node(n: *FragilityNode) -> i64 {
119 // SINGLE_SOURCE_HIGH on critical path = CRITICAL
120 if n.is_critical_path == 1 {
121 if n.supply_risk == 1 { return NX_FRAG_CRITICAL } // SINGLE_SOURCE_HIGH
122 if n.supply_risk == 2 { return NX_FRAG_HIGH } // SINGLE_SOURCE_LOW on critical
123 if n.knowledge_opacity == 2 { return NX_FRAG_CRITICAL }
124 }
125 // Non-critical SINGLE_SOURCE_HIGH = HIGH (still nervous)
126 if n.supply_risk == 1 { return NX_FRAG_HIGH }
127 if n.supply_risk == 2 { return NX_FRAG_MODERATE }
128 // Knowledge opacity hides the dependency
129 if n.knowledge_opacity == 2 { return NX_FRAG_HIGH }
130 if n.knowledge_opacity == 1 { return NX_FRAG_MODERATE }
131 // Time-degradation: orthodox-grain at 5%MC/-18C is FINE; high-MC warm is CRITICAL
132 if n.time_to_decay_days > 0 {
133 if n.time_to_decay_days < 30 { return NX_FRAG_CRITICAL }
134 if n.time_to_decay_days < 180 { return NX_FRAG_HIGH }
135 if n.time_to_decay_days < 730 { return NX_FRAG_MODERATE }
136 }
137 return NX_FRAG_NEGLIGIBLE
138}
139
140// ===== Dominant fragility kind for a node =========================
141
142func nx_fragility_dominant_kind(n: *FragilityNode) -> i64 {
143 if n.supply_risk == 1 { return NX_FRAG_KIND_SINGLE_SOURCE }
144 if n.is_critical_path == 1 {
145 if n.alternates_count == 0 { return NX_FRAG_KIND_SINGLE_POINT_OF_FAIL }
146 }
147 if n.knowledge_opacity >= 1 { return NX_FRAG_KIND_KNOWLEDGE_OPACITY }
148 if n.externality_score_q10 > 5120 { return NX_FRAG_KIND_HIDDEN_EXTERNALITY } // > 5
149 if n.time_to_decay_days > 0 {
150 if n.time_to_decay_days < 365 { return NX_FRAG_KIND_TIME_DEGRADATION }
151 }
152 if n.supply_risk == 2 { return NX_FRAG_KIND_SINGLE_SOURCE }
153 if n.competency_required > 1 { return NX_FRAG_KIND_MISSING_COMPETENCY }
154 return 0 // no dominant fragility
155}
156
157// ===== Graph-level dispatcher =====================================
158//
159// Takes an array of FragilityNode + count, returns the WORST verdict
160// across all nodes. Real implementation walks the graph; this v1
161// scans the node array (caller has already mapped graph to nodes).
162
163func nx_fragility_graph_verdict(nodes: **FragilityNode, n_nodes: i64) -> i64 {
164 if n_nodes <= 0 { return NX_FRAG_UNKNOWN }
165 var worst: i64 = NX_FRAG_NEGLIGIBLE
166 var i: i64 = 0
167 var iter: i64 = 0
168 var verdict: i64 = 0
169 let budget: i64 = 4096 // bounded loop per [[feedback-bounded-loop-discipline-jpl-rule-2]]
170 while verdict == 0 && iter < budget {
171 if i >= n_nodes { verdict = 1 }
172 if verdict == 0 {
173 let node: *FragilityNode = nodes[i]
174 let v: i64 = nx_fragility_classify_node(node)
175 if v > worst { worst = v }
176 i = i + 1
177 }
178 iter = iter + 1
179 }
180 return worst
181}
182
183// ===== Critical-path bus-factor calculator =========================
184//
185// Bus factor = smallest set of node removals that disconnects the
186// goal from the player. v1: count of is_critical_path nodes that
187// are SINGLE_SOURCE_HIGH (these are the "if any breaks, you fail"
188// nodes). v2: graph-cut algorithm (queued).
189
190func nx_fragility_bus_factor(nodes: **FragilityNode, n_nodes: i64) -> i64 {
191 var count: i64 = 0
192 var i: i64 = 0
193 var iter: i64 = 0
194 var verdict: i64 = 0
195 let budget: i64 = 4096
196 while verdict == 0 && iter < budget {
197 if i >= n_nodes { verdict = 1 }
198 if verdict == 0 {
199 let node: *FragilityNode = nodes[i]
200 if node.is_critical_path == 1 {
201 if node.supply_risk == 1 { count = count + 1 }
202 }
203 i = i + 1
204 }
205 iter = iter + 1
206 }
207 return count
208}
209
210// ===== Toaster Project applied to Potato Cell example =============
211//
212// docs/POTATO_CELL_V1_SPEC.md ships an open BOM with all components
213// at supply_risk >= DUAL_SOURCE per Pillar 9 cardinal. Running this
214// primitive against the Potato Cell BOM should yield FRAG_NEGLIGIBLE
215// or FRAG_MODERATE -- if it yields HIGH or CRITICAL, Pillar 9 has
216// been violated and the substrate refuses to ship.
217//
218// In-game variant: Thwaites player tries to build a Potato Cell with
219// only Amazon prime as supplier. Substrate identifies (a) the LTE
220// modem requires SIM card (which requires telecom contract), (b) the
221// LED driver requires Mean Well (Taiwan-only mfg), (c) the ESP32-S3
222// requires Espressif (China-only mfg). Three SINGLE_SOURCE_HIGH on
223// critical path = FRAG_CRITICAL. Curriculum unit: "How do you build
224// a system whose supply chain you actually control?"
225
226// ===== Curriculum-export helper ===================================
227//
228// For the game's curriculum module: given a node, returns the
229// "lesson handle" describing what the player learns by encountering
230// this fragility. Composer in nishi-engine/nx/curriculum.nx (queued).
231
232const NX_LESSON_SOURCE_DIVERSITY: i64 = 1
233const NX_LESSON_REDUNDANCY_BUDGETING: i64 = 2
234const NX_LESSON_EXTERNALITY_ACCOUNTING: i64 = 3
235const NX_LESSON_SKILL_PREREQUISITES: i64 = 4
236const NX_LESSON_COUPLING_AWARENESS: i64 = 5
237const NX_LESSON_TIME_PRESSURE: i64 = 6
238const NX_LESSON_TRANSPARENCY: i64 = 7
239
240func nx_fragility_lesson_handle(kind: i64) -> i64 {
241 if kind == NX_FRAG_KIND_SINGLE_SOURCE { return NX_LESSON_SOURCE_DIVERSITY }
242 if kind == NX_FRAG_KIND_SINGLE_POINT_OF_FAIL { return NX_LESSON_REDUNDANCY_BUDGETING }
243 if kind == NX_FRAG_KIND_HIDDEN_EXTERNALITY { return NX_LESSON_EXTERNALITY_ACCOUNTING }
244 if kind == NX_FRAG_KIND_MISSING_COMPETENCY { return NX_LESSON_SKILL_PREREQUISITES }
245 if kind == NX_FRAG_KIND_TIGHT_COUPLING { return NX_LESSON_COUPLING_AWARENESS }
246 if kind == NX_FRAG_KIND_TIME_DEGRADATION { return NX_LESSON_TIME_PRESSURE }
247 if kind == NX_FRAG_KIND_KNOWLEDGE_OPACITY { return NX_LESSON_TRANSPARENCY }
248 return 0
249}