nx_maint_ledger.nx source
↩ module page · 137 lines · 4566 B
1// nx_maint_ledger.nx -- R4: the additive maintenance ledger (#13, history is
2// sacred). Append-only record of every asset's health results over time. Its
3// value is PREDICTIVE: a worsening severity trend warns BEFORE failure, and a
4// past SAFETY event is NEVER forgotten even after the asset recovers.
5//
6// Operator (2026-06-23): build "from the hardware rung up each rung" -- this is
7// R4 on top of R3's unified MaintResult. CLAUDE.md #13: additive-only, never
8// DELETE; history enables debugging, rollback, and degradation analysis.
9//
10// (Pure-buffer ledger LOGIC + trend; durable seg-store backing is a thin next
11// layer, same append-to-store pattern as nx_ciq_field_maintenance.)
12//
13// NEVER-BRICK (#26): append + query; no syscalls, no device writes, NO delete.
14//
15// genealogy_id: project-maintenance-platform-sclass-2026-06-23 (R4 ledger)
16// license_tier: ORIGINAL
17
18import "nx_maint_unify.nx" // NX_HEALTH_* severities (stored), MaintResult shape
19
20// ---- Severity trend over an asset's history ------------------------
21const NX_TREND_NODATA: i64 = 0
22const NX_TREND_IMPROVING: i64 = 1
23const NX_TREND_STABLE: i64 = 2
24const NX_TREND_WORSENING: i64 = 3
25const NX_TREND_N: i64 = 4
26
27func nx_trend_is_valid(t: i64) -> i64 {
28 if t < 0 { return 0 }
29 if t >= NX_TREND_N { return 0 }
30 return 1
31}
32
33struct LedgerEntry {
34 asset_id: i64,
35 ts: i64,
36 severity: i64, // NX_HEALTH_*
37 kernel: i64,
38 native_verdict: i64,
39 headline: i64,
40}
41
42struct Ledger {
43 cap: i64,
44 count: i64,
45}
46
47func nx_ledger_init(led: *Ledger, cap: i64) -> i64 {
48 led.cap = cap
49 led.count = 0
50 return 0
51}
52
53// Append a result (ADDITIVE -- never overwrites a prior entry). Returns the
54// new entry index (>=0), or -1 if the ledger is full (still no overwrite).
55func nx_ledger_append(led: *Ledger, entries: *LedgerEntry, asset_id: i64, ts: i64,
56 severity: i64, kernel: i64, native_verdict: i64, headline: i64) -> i64 {
57 if led.count >= led.cap {
58 return 0 - 1
59 }
60 let i: i64 = led.count
61 entries[i].asset_id = asset_id
62 entries[i].ts = ts
63 entries[i].severity = severity
64 entries[i].kernel = kernel
65 entries[i].native_verdict = native_verdict
66 entries[i].headline = headline
67 led.count = led.count + 1
68 return i
69}
70
71func nx_ledger_count_for(led: *Ledger, entries: *LedgerEntry, asset_id: i64) -> i64 {
72 var c: i64 = 0
73 var i: i64 = 0
74 while i < led.count {
75 if entries[i].asset_id == asset_id { c = c + 1 }
76 i = i + 1
77 }
78 return c
79}
80
81// Worst (max) severity EVER recorded for an asset -- history sacred: a past
82// hazard is never forgotten even if the latest reading is OK. -1 if none.
83func nx_ledger_worst_for(led: *Ledger, entries: *LedgerEntry, asset_id: i64) -> i64 {
84 var w: i64 = 0 - 1
85 var i: i64 = 0
86 while i < led.count {
87 if entries[i].asset_id == asset_id {
88 if entries[i].severity > w { w = entries[i].severity }
89 }
90 i = i + 1
91 }
92 return w
93}
94
95// Most recent entry for an asset -> *out. Returns 1 if found, 0 if none.
96func nx_ledger_latest_for(led: *Ledger, entries: *LedgerEntry, asset_id: i64, out: *LedgerEntry) -> i64 {
97 var i: i64 = led.count - 1
98 while i >= 0 {
99 if entries[i].asset_id == asset_id {
100 out.asset_id = entries[i].asset_id
101 out.ts = entries[i].ts
102 out.severity = entries[i].severity
103 out.kernel = entries[i].kernel
104 out.native_verdict = entries[i].native_verdict
105 out.headline = entries[i].headline
106 return 1
107 }
108 i = i - 1
109 }
110 return 0
111}
112
113// Recent direction for an asset: latest vs the immediately-previous reading.
114// WORSENING (degradation -> act before failure), IMPROVING, STABLE, or NODATA.
115func nx_ledger_trend_for(led: *Ledger, entries: *LedgerEntry, asset_id: i64) -> i64 {
116 var latest_sev: i64 = 0 - 1
117 var prev_sev: i64 = 0 - 1
118 var found: i64 = 0
119 var i: i64 = led.count - 1
120 while i >= 0 {
121 if entries[i].asset_id == asset_id {
122 if found == 0 {
123 latest_sev = entries[i].severity
124 found = 1
125 } else {
126 prev_sev = entries[i].severity
127 found = 2
128 i = 0 - 1
129 }
130 }
131 i = i - 1
132 }
133 if found < 2 { return NX_TREND_NODATA }
134 if latest_sev > prev_sev { return NX_TREND_WORSENING }
135 if latest_sev < prev_sev { return NX_TREND_IMPROVING }
136 return NX_TREND_STABLE
137}