code wiki / (root) / nx_remedy.nx

nx_remedy.nx source

↩ module page · 572 lines · 21635 B

1// nx_remedy.nx -- formalized self-remediation cycle as substrate primitives. 2// 3// Implements the SELF_REMEDIATION_AS_MATHEMATICIAN cardinal as a sequence 4// of named primitives, so NishiLang itself can run the 7-phase cycle 5// autonomously -- no AI in the loop. Every fix is principled, every 6// recommendation cites the math, every applied change is A/B verified 7// AND generalization-proven before adoption. 8// 9// CYCLE PHASES (mirror docs/SELF_REMEDIATION_AS_MATHEMATICIAN.md): 10// 1. detect -- anomaly check vs baseline / threshold / regression 11// 2. diagnose -- classify into closed RootCause taxonomy 12// 3. propose -- rank candidate FixClass options w/ cost+effectiveness 13// 4. apply -- emit ActionPlan struct for the top proposal 14// 5. ab_verify -- before/after measurement verdict 15// 6. generalize -- grid coverage proof (no single-point overfitting) 16// 7. record -- structured provenance ledger entry 17// 18// Closed taxonomies (also numeric IDs for fast dispatch): 19// AnomalyKind : ANOMALY_NONE / TOLERANCE_EXCEEDED / REGRESSION / 20// STATISTICAL_OUTLIER / TIER_OVERFLOW / TIER_UNDERFLOW / 21// CALIBRATION_MISMATCH 22// RootCause : RC01..RC14 (NumericOverflow ... StaleCalibration) 23// FixClass : FX01..FX13 (EscalateNumericTier ... Ensemble) 24// ApplyVerdict : WORKED / NO_CHANGE / REGRESSION 25// GenVerdict : PASSES_GRID / FAILS_AT_CELL 26// CycleVerdict : ADOPT / REJECT_FAILED_AB / REJECT_FAILED_GEN / NO_ACTION_NEEDED 27// 28// genealogy_id: lehmann_casella_1998 (estimator theory) + 29// aristotelian_root_cause (4-cause taxonomy) + 30// toyota_production_system_andon (halt-on-anomaly) 31// lineage_id: estimator_theory + change_impact_measurement 32 33// nx_safety_envelope: 34// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 35// sil_target: SIL1 36// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 37// verdict: NOT_YET_EVALUATED 38 39import "syscalls.nx" 40import "nx_estimator_diagnostics.nx" 41const NX_MAGIC_950000000: i64 = 950000000 42const NX_MAGIC_900000000: i64 = 900000000 43const NX_MAGIC_700000000: i64 = 700000000 44const NX_MAGIC_800000000: i64 = 800000000 45const NX_MAGIC_500000000: i64 = 500000000 46const NX_MAGIC_990000000: i64 = 990000000 47const NX_MAGIC_600000000: i64 = 600000000 48const NX_MAGIC_850000000: i64 = 850000000 49const NX_MAGIC_750000000: i64 = 750000000 50 51// ===== sealed enums (named codes) ===== 52 53const NX_ANOMALY_NONE: i64 = 0 54const NX_ANOMALY_TOLERANCE_EXCEEDED: i64 = 1 55const NX_ANOMALY_REGRESSION: i64 = 2 56const NX_ANOMALY_STATISTICAL_OUTLIER: i64 = 3 57const NX_ANOMALY_TIER_OVERFLOW: i64 = 4 58const NX_ANOMALY_TIER_UNDERFLOW: i64 = 5 59const NX_ANOMALY_CALIBRATION_MISMATCH: i64 = 6 60 61const NX_RC_NUMERIC_OVERFLOW: i64 = 1 62const NX_RC_NUMERIC_UNDERFLOW: i64 = 2 63const NX_RC_PRECISION_LOSS: i64 = 3 64const NX_RC_HASH_QUALITY_BIAS: i64 = 4 65const NX_RC_SAMPLE_DENSITY_TOO_LOW: i64 = 5 66const NX_RC_SAMPLE_VARIANCE_TOO_HIGH: i64 = 6 67const NX_RC_SIGN_HANDLING_ERROR: i64 = 7 68const NX_RC_OFF_BY_ONE: i64 = 8 69const NX_RC_ALGORITHM_ASYMPTOTIC: i64 = 9 70const NX_RC_DOMAIN_VIOLATED: i64 = 10 71const NX_RC_ESTIMATOR_MISMATCH: i64 = 11 72const NX_RC_SYSTEMIC_HW_LIMIT: i64 = 12 73const NX_RC_RACE_OR_ORDERING: i64 = 13 74const NX_RC_STALE_CALIBRATION: i64 = 14 75 76const NX_FX_ESCALATE_TIER: i64 = 1 77const NX_FX_SWAP_ALGORITHM: i64 = 2 78const NX_FX_DENSER_SAMPLING: i64 = 3 79const NX_FX_MORE_SEEDS_PER_SAMPLE: i64 = 4 80const NX_FX_SWAP_HASH_FAMILY: i64 = 5 81const NX_FX_REFORMULATE_MATH: i64 = 6 82const NX_FX_FIX_SIGN_HANDLING: i64 = 7 83const NX_FX_DEFEND_DOMAIN: i64 = 8 84const NX_FX_UPGRADE_Q_SCALE: i64 = 9 85const NX_FX_UNLOCK_TIER_ADAPTER: i64 = 10 86const NX_FX_RECALIBRATE_TABLE: i64 = 11 87const NX_FX_TIGHTEN_SYNC: i64 = 12 88const NX_FX_ENSEMBLE: i64 = 13 89 90const NX_APPLY_WORKED: i64 = 0 91const NX_APPLY_NO_CHANGE: i64 = 1 92const NX_APPLY_REGRESSION: i64 = 2 93 94const NX_GEN_PASSES: i64 = 0 95const NX_GEN_FAILS_AT_CELL: i64 = 1 96 97const NX_CYCLE_ADOPT: i64 = 0 98const NX_CYCLE_REJECT_FAILED_AB: i64 = 1 99const NX_CYCLE_REJECT_FAILED_GEN: i64 = 2 100const NX_CYCLE_NO_ACTION_NEEDED: i64 = 3 101 102// ===== structs ===== 103 104struct AnomalyReport { 105 kind: i64, // sealed enum 106 observed: i64, // measured metric (e.g., mean error) 107 baseline: i64, // baseline / target metric 108 tolerance: i64, // ppb deviation allowed 109 cell_id: i64, // which grid cell triggered (for cross-ref) 110} 111 112struct DiagnosisReport { 113 rc_code: i64, // sealed RC enum 114 confidence_ppb: i64, // diagnostic confidence 115 contributing_axes: i64, // bitmask of which axes drove the verdict 116} 117 118struct FixProposal { 119 fx_code: i64, // sealed FX enum 120 rank: i64, // 0-indexed (lower is better) 121 cost_score: i64, // relative cost (smaller better) 122 effectiveness: i64, // expected reduction in error metric, ppb 123} 124 125struct ActionPlan { 126 fx_code: i64, 127 payload_a: i64, // FX-specific parameter (e.g., target tier) 128 payload_b: i64, // FX-specific parameter (e.g., new sample count) 129} 130 131struct CycleOutcome { 132 verdict: i64, // sealed CycleVerdict 133 applied_fx: i64, // fx_code that was applied (or 0) 134 apply_verdict: i64, // sealed ApplyVerdict 135 gen_verdict: i64, // sealed GenVerdict 136 failed_cell_id: i64, // if gen_verdict == FAILS_AT_CELL 137} 138 139// ===== allocators ===== 140 141func nx_anomaly_alloc() -> *AnomalyReport { 142 let raw: *u8 = sys_mmap(40) 143 let a: *AnomalyReport = raw as *AnomalyReport 144 a.kind = 0 145 a.observed = 0 146 a.baseline = 0 147 a.tolerance = 0 148 a.cell_id = -1 149 return a 150} 151 152func nx_diag_report_alloc() -> *DiagnosisReport { 153 let raw: *u8 = sys_mmap(24) 154 let d: *DiagnosisReport = raw as *DiagnosisReport 155 d.rc_code = 0 156 d.confidence_ppb = 0 157 d.contributing_axes = 0 158 return d 159} 160 161func nx_plan_alloc() -> *ActionPlan { 162 let raw: *u8 = sys_mmap(24) 163 let p: *ActionPlan = raw as *ActionPlan 164 p.fx_code = 0 165 p.payload_a = 0 166 p.payload_b = 0 167 return p 168} 169 170func nx_cycle_outcome_alloc() -> *CycleOutcome { 171 let raw: *u8 = sys_mmap(40) 172 let o: *CycleOutcome = raw as *CycleOutcome 173 o.verdict = NX_CYCLE_NO_ACTION_NEEDED 174 o.applied_fx = 0 175 o.apply_verdict = NX_APPLY_NO_CHANGE 176 o.gen_verdict = NX_GEN_PASSES 177 o.failed_cell_id = -1 178 return o 179} 180 181// ===== PHASE 1: detect ============================================== 182// 183// Classifies a measurement against a baseline + tolerance into an 184// AnomalyKind. observed > baseline * (1 + tolerance_ppb / 1e9) -> 185// TOLERANCE_EXCEEDED. observed > prior_baseline_for_same_cell -> 186// REGRESSION. 187 188func nx_remedy_detect(observed: i64, baseline: i64, tolerance_ppb: i64, 189 prior: i64, report: *AnomalyReport, cell_id: i64) -> i64 { 190 report.observed = observed 191 report.baseline = baseline 192 report.tolerance = tolerance_ppb 193 report.cell_id = cell_id 194 195 // Tolerance check: |observed - baseline| / baseline > tolerance_ppb. 196 var abs_dev: i64 = observed - baseline 197 if abs_dev < 0 { abs_dev = -abs_dev } 198 let abs_baseline: i64 = baseline 199 // dev_ppb = abs_dev * 1e9 / |baseline|. Guard against baseline == 0. 200 if abs_baseline > 0 { 201 let dev_ppb: i64 = (abs_dev * NX_DIAG_PPB_SCALE) / abs_baseline 202 if dev_ppb > tolerance_ppb { 203 report.kind = NX_ANOMALY_TOLERANCE_EXCEEDED 204 return 0 205 } 206 } 207 // Regression check: observed worse than prior measurement at same cell. 208 // 'Worse' means observed > prior by > 1 unit. 209 if prior > 0 { 210 if observed > prior + 1 { 211 report.kind = NX_ANOMALY_REGRESSION 212 return 0 213 } 214 } 215 report.kind = NX_ANOMALY_NONE 216 return 0 217} 218 219// ===== PHASE 2: diagnose ============================================ 220// 221// Maps an anomaly + an estimator comparison verdict (from 222// nx_diag_compare) into a RootCause class. 223// 224// Decision table: 225// anomaly == TIER_OVERFLOW -> RC01 NumericOverflow 226// anomaly == CALIBRATION_MISMATCH -> RC14 StaleCalibration 227// ours vs theirs HIGHER_BIAS_LOWER_VAR -> RC14 StaleCalibration 228// OR RC11 EstimatorMismatch 229// (use confidence to pick) 230// ours vs theirs LOWER_BIAS_HIGHER_VAR -> RC06 SampleVarianceTooHigh 231// OR RC11 EstimatorMismatch 232// ours vs theirs BOTH_WORSE -> RC11 EstimatorMismatch 233// default -> RC11 EstimatorMismatch 234 235func nx_remedy_diagnose(anomaly: *AnomalyReport, comparison_verdict: i64, 236 report: *DiagnosisReport) -> i64 { 237 report.contributing_axes = 0 238 239 if anomaly.kind == NX_ANOMALY_TIER_OVERFLOW { 240 report.rc_code = NX_RC_NUMERIC_OVERFLOW 241 report.confidence_ppb = NX_MAGIC_950000000 // 0.95 confidence 242 return 0 243 } 244 if anomaly.kind == NX_ANOMALY_CALIBRATION_MISMATCH { 245 report.rc_code = NX_RC_STALE_CALIBRATION 246 report.confidence_ppb = NX_MAGIC_900000000 247 return 0 248 } 249 if comparison_verdict == NX_DIAG_VERDICT_HIGHER_BIAS_LOWER_VAR { 250 report.rc_code = NX_RC_STALE_CALIBRATION 251 report.confidence_ppb = NX_MAGIC_700000000 // 0.70: could also be RC11 252 report.contributing_axes = 1 // bias axis 253 return 0 254 } 255 if comparison_verdict == NX_DIAG_VERDICT_LOWER_BIAS_HIGHER_VAR { 256 report.rc_code = NX_RC_SAMPLE_VARIANCE_TOO_HIGH 257 report.confidence_ppb = NX_MAGIC_700000000 258 report.contributing_axes = 2 // variance axis 259 return 0 260 } 261 if comparison_verdict == NX_DIAG_VERDICT_BOTH_WORSE { 262 report.rc_code = NX_RC_ESTIMATOR_MISMATCH 263 report.confidence_ppb = NX_MAGIC_800000000 264 report.contributing_axes = 3 // both axes 265 return 0 266 } 267 report.rc_code = NX_RC_ESTIMATOR_MISMATCH 268 report.confidence_ppb = NX_MAGIC_500000000 269 return 0 270} 271 272// ===== PHASE 3: propose ============================================ 273// 274// Given the diagnosed RC code, returns a ranked array of FixProposal 275// (top recommendation first). Caller passes pre-allocated array of 276// at least 3 slots; we fill how many we have proposals for, return 277// the count. 278 279// FixProposal is 4 i64 fields = 32 bytes per element. Use a helper 280// to write into the array since NishiLang lacks array[i].field 281// chained syntax. 282const NX_FIX_PROPOSAL_BYTES: i64 = 32 283 284func nx_remedy_fp_at(arr: *FixProposal, i: i64) -> *FixProposal { 285 return (((arr as i64) + i * NX_FIX_PROPOSAL_BYTES) as *FixProposal) 286} 287 288func nx_remedy_fp_set(arr: *FixProposal, i: i64, 289 fx: i64, rank: i64, cost: i64, eff: i64) -> i64 { 290 let p: *FixProposal = nx_remedy_fp_at(arr, i) 291 p.fx_code = fx 292 p.rank = rank 293 p.cost_score = cost 294 p.effectiveness = eff 295 return 0 296} 297 298func nx_remedy_propose(rc_code: i64, out: *FixProposal, max_n: i64) -> i64 { 299 if max_n <= 0 { return 0 } 300 if rc_code == NX_RC_NUMERIC_OVERFLOW { 301 nx_remedy_fp_set(out, 0, NX_FX_ESCALATE_TIER, 0, 10, NX_MAGIC_990000000) 302 if max_n >= 2 { 303 nx_remedy_fp_set(out, 1, NX_FX_UPGRADE_Q_SCALE, 1, 1, NX_MAGIC_700000000) 304 } 305 if max_n >= 3 { 306 nx_remedy_fp_set(out, 2, NX_FX_REFORMULATE_MATH, 2, 3, NX_MAGIC_600000000) 307 } 308 if max_n >= 3 { return 3 } 309 if max_n >= 2 { return 2 } 310 return 1 311 } 312 if rc_code == NX_RC_STALE_CALIBRATION { 313 nx_remedy_fp_set(out, 0, NX_FX_RECALIBRATE_TABLE, 0, 5, NX_MAGIC_900000000) 314 if max_n >= 2 { 315 nx_remedy_fp_set(out, 1, NX_FX_REFORMULATE_MATH, 1, 3, NX_MAGIC_600000000) 316 } 317 if max_n >= 2 { return 2 } 318 return 1 319 } 320 if rc_code == NX_RC_SAMPLE_VARIANCE_TOO_HIGH { 321 nx_remedy_fp_set(out, 0, NX_FX_ENSEMBLE, 0, 8, NX_MAGIC_850000000) 322 if max_n >= 2 { 323 nx_remedy_fp_set(out, 1, NX_FX_MORE_SEEDS_PER_SAMPLE, 1, 5, NX_MAGIC_500000000) 324 } 325 if max_n >= 2 { return 2 } 326 return 1 327 } 328 if rc_code == NX_RC_ESTIMATOR_MISMATCH { 329 nx_remedy_fp_set(out, 0, NX_FX_SWAP_ALGORITHM, 0, 20, NX_MAGIC_800000000) 330 return 1 331 } 332 if rc_code == NX_RC_HASH_QUALITY_BIAS { 333 nx_remedy_fp_set(out, 0, NX_FX_SWAP_HASH_FAMILY, 0, 5, NX_MAGIC_700000000) 334 return 1 335 } 336 if rc_code == NX_RC_SAMPLE_DENSITY_TOO_LOW { 337 nx_remedy_fp_set(out, 0, NX_FX_DENSER_SAMPLING, 0, 5, NX_MAGIC_750000000) 338 return 1 339 } 340 // default: no specific recommendation. 341 return 0 342} 343 344// ===== PHASE 4: apply (plan emission only) ========================= 345// 346// The substrate itself doesn't self-modify code today; this primitive 347// emits an ActionPlan struct the runner can dispatch. Payloads are 348// FX-specific: 349// FX01 EscalateTier payload_a = target tier (N2/N3/N4/N5) 350// FX03 DenserSampling payload_a = target sample count 351// FX04 MoreSeedsPerSample payload_a = target seed count 352// FX11 RecalibrateTable payload_a = target sample density 353// payload_b = target seed count 354// Defaults provided; caller can override. 355 356func nx_remedy_apply_plan(top: *FixProposal, plan: *ActionPlan) -> i64 { 357 plan.fx_code = top.fx_code 358 plan.payload_a = 0 359 plan.payload_b = 0 360 if top.fx_code == NX_FX_ESCALATE_TIER { plan.payload_a = 2 } // -> N2 (i128) 361 if top.fx_code == NX_FX_DENSER_SAMPLING { plan.payload_a = 257 } // DS density 362 if top.fx_code == NX_FX_MORE_SEEDS_PER_SAMPLE { plan.payload_a = 500 } 363 if top.fx_code == NX_FX_RECALIBRATE_TABLE { 364 plan.payload_a = 192 365 plan.payload_b = 500 366 } 367 return 0 368} 369 370// ===== PHASE 5: A/B verify ========================================= 371// 372// Compares before/after EstimatorDiag. Verdict: 373// after.mse < before.mse * (1 - improvement_threshold_ppb) -> WORKED 374// after.mse > before.mse * (1 + regression_threshold_ppb) -> REGRESSION 375// otherwise -> NO_CHANGE 376 377func nx_remedy_ab_verify(before: *EstimatorDiag, after: *EstimatorDiag, 378 improvement_thresh_ppb: i64, 379 regression_thresh_ppb: i64) -> i64 { 380 // upper = before.mse * (1 + regression_thresh_ppb / 1e9) 381 let upper_delta: i64 = (before.mse * regression_thresh_ppb) / NX_DIAG_PPB_SCALE 382 let upper: i64 = before.mse + upper_delta 383 let lower_delta: i64 = (before.mse * improvement_thresh_ppb) / NX_DIAG_PPB_SCALE 384 let lower: i64 = before.mse - lower_delta 385 if after.mse < lower { 386 return NX_APPLY_WORKED 387 } 388 if after.mse > upper { 389 return NX_APPLY_REGRESSION 390 } 391 return NX_APPLY_NO_CHANGE 392} 393 394// ===== PHASE 6: generalization proof ============================== 395// 396// Given an array of paired before/after EstimatorDiag for N grid 397// cells, returns PASSES_GRID iff every cell did not regress (no 398// REGRESSION verdict in any cell). Caller passes regression 399// tolerance. Returns the failing cell index in out_cell or -1. 400 401func nx_remedy_generalize(befores: **EstimatorDiag, afters: **EstimatorDiag, 402 n_cells: i64, regression_thresh_ppb: i64, 403 out_cell: *i64) -> i64 { 404 var i: i64 = 0 405 while i < n_cells { 406 let v: i64 = nx_remedy_ab_verify(befores[i], afters[i], 407 0, regression_thresh_ppb) 408 if v == NX_APPLY_REGRESSION { 409 out_cell[0] = i 410 return NX_GEN_FAILS_AT_CELL 411 } 412 i = i + 1 413 } 414 out_cell[0] = -1 415 return NX_GEN_PASSES 416} 417 418// ===== PHASE 7: record ledger entry (decimal-emit JSON-line) ====== 419// 420// Emits a JSON-line provenance entry to file descriptor fd. Format: 421// {"phase":"REMEDY","cycle":<id>,"rc":N,"fx":N,"apply":N,"gen":N, 422// "before_mse":N,"after_mse":N,"failed_cell":N,"cell_id":N} 423 424func rl_putc(fd: i64, c: i64) -> i64 { 425 let buf: *u8 = sys_mmap(1) 426 buf[0] = c & 0xFF 427 sys_write(fd, buf, 1) 428 return 0 429} 430 431func rl_str(fd: i64, s: *u8, len: i64) -> i64 { 432 sys_write(fd, s, len) 433 return 0 434} 435 436func rl_i64(fd: i64, n: i64) -> i64 { 437 if n < 0 { 438 rl_putc(fd, 45) 439 return rl_i64(fd, -n) 440 } 441 if n == 0 { 442 rl_putc(fd, 48) 443 return 0 444 } 445 let digits: *u8 = sys_mmap(32) 446 var d: i64 = 0 447 var v: i64 = n 448 while v > 0 { 449 digits[d] = (v % 10) + 48 450 v = v / 10 451 d = d + 1 452 } 453 while d > 0 { 454 d = d - 1 455 rl_putc(fd, digits[d]) 456 } 457 return 0 458} 459 460func nx_remedy_record(fd: i64, cycle_id: i64, anomaly: *AnomalyReport, 461 diagnosis: *DiagnosisReport, plan: *ActionPlan, 462 outcome: *CycleOutcome, 463 before: *EstimatorDiag, after: *EstimatorDiag) -> i64 { 464 rl_str(fd, "{\"phase\":\"REMEDY\",\"cycle\":", 26) 465 rl_i64(fd, cycle_id) 466 rl_str(fd, ",\"cell\":", 8) 467 rl_i64(fd, anomaly.cell_id) 468 rl_str(fd, ",\"rc\":", 6) 469 rl_i64(fd, diagnosis.rc_code) 470 rl_str(fd, ",\"fx\":", 6) 471 rl_i64(fd, plan.fx_code) 472 rl_str(fd, ",\"apply\":", 9) 473 rl_i64(fd, outcome.apply_verdict) 474 rl_str(fd, ",\"gen\":", 7) 475 rl_i64(fd, outcome.gen_verdict) 476 rl_str(fd, ",\"before_mse\":", 14) 477 rl_i64(fd, before.mse) 478 rl_str(fd, ",\"after_mse\":", 13) 479 rl_i64(fd, after.mse) 480 rl_str(fd, ",\"failed_cell\":", 15) 481 rl_i64(fd, outcome.failed_cell_id) 482 rl_str(fd, ",\"verdict\":", 11) 483 rl_i64(fd, outcome.verdict) 484 rl_str(fd, "}\n", 2) 485 return 0 486} 487 488// ===== full cycle runner ============================================= 489// 490// Drives all 7 phases. Caller provides: 491// before : EstimatorDiag from current state 492// after : EstimatorDiag from proposed-state simulation 493// grid_before/after : per-cell EstimatorDiag pairs for the 494// generalization proof 495// ref : reference estimator (e.g., DS) for comparison-based 496// diagnosis 497// tolerance_ppb / regression_thresh_ppb / improvement_thresh_ppb : 498// substrate-level thresholds 499// cycle_id, cell_id : caller-assigned IDs for the ledger entry 500// fd_ledger : open file descriptor to emit ledger entry to 501// 502// Returns CycleOutcome via the `out` pointer. 503 504func nx_remedy_cycle_run( 505 before: *EstimatorDiag, after: *EstimatorDiag, ref: *EstimatorDiag, 506 grid_before: **EstimatorDiag, grid_after: **EstimatorDiag, n_cells: i64, 507 tolerance_ppb: i64, 508 regression_thresh_ppb: i64, 509 improvement_thresh_ppb: i64, 510 cycle_id: i64, cell_id: i64, 511 fd_ledger: i64, 512 out: *CycleOutcome) -> i64 513{ 514 // Phase 1: detect 515 let anomaly: *AnomalyReport = nx_anomaly_alloc() 516 nx_remedy_detect(before.max_abs_err, ref.max_abs_err, 517 tolerance_ppb, 0, anomaly, cell_id) 518 if anomaly.kind == NX_ANOMALY_NONE { 519 out.verdict = NX_CYCLE_NO_ACTION_NEEDED 520 return 0 521 } 522 523 // Phase 2: diagnose (compare vs reference) 524 let cmp_verdict: i64 = nx_diag_compare(before, ref) 525 let diag: *DiagnosisReport = nx_diag_report_alloc() 526 nx_remedy_diagnose(anomaly, cmp_verdict, diag) 527 528 // Phase 3: propose (top 3, take rank-0) 529 let proposals: *FixProposal = (sys_mmap(96)) as *FixProposal 530 let n_props: i64 = nx_remedy_propose(diag.rc_code, proposals, 3) 531 if n_props <= 0 { 532 out.verdict = NX_CYCLE_NO_ACTION_NEEDED 533 return 0 534 } 535 536 // Phase 4: apply (emit plan) 537 let plan: *ActionPlan = nx_plan_alloc() 538 let top: *FixProposal = (((proposals as i64) + 0) as *FixProposal) 539 nx_remedy_apply_plan(top, plan) 540 out.applied_fx = plan.fx_code 541 542 // Phase 5: A/B verify (caller provides post-fix `after` snapshot) 543 out.apply_verdict = nx_remedy_ab_verify(before, after, 544 improvement_thresh_ppb, 545 regression_thresh_ppb) 546 if out.apply_verdict == NX_APPLY_REGRESSION { 547 out.verdict = NX_CYCLE_REJECT_FAILED_AB 548 nx_remedy_record(fd_ledger, cycle_id, anomaly, diag, plan, 549 out, before, after) 550 return 0 551 } 552 553 // Phase 6: generalization 554 let failed_cell: *i64 = (sys_mmap(8)) as *i64 555 failed_cell[0] = -1 556 out.gen_verdict = nx_remedy_generalize(grid_before, grid_after, 557 n_cells, regression_thresh_ppb, 558 failed_cell) 559 out.failed_cell_id = failed_cell[0] 560 if out.gen_verdict == NX_GEN_FAILS_AT_CELL { 561 out.verdict = NX_CYCLE_REJECT_FAILED_GEN 562 nx_remedy_record(fd_ledger, cycle_id, anomaly, diag, plan, 563 out, before, after) 564 return 0 565 } 566 567 // Phase 7: record adoption 568 out.verdict = NX_CYCLE_ADOPT 569 nx_remedy_record(fd_ledger, cycle_id, anomaly, diag, plan, out, 570 before, after) 571 return 0 572}