code wiki / (root) / nx_fragility_analysis.nx

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}