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}