code wiki / (root) / nx_session_summary.nx

nx_session_summary.nx source

↩ module page · 142 lines · 4469 B

1// nx_session_summary.nx -- periodic session-end summary emitter. 2// 3// Per [[feedback-hunter-gatherer-meta-primitives-outward-inward-axes]] 4// "every session emits both a hunt-log AND a gather-log entry or 5// it's a non-session." The session-summary primitive emits a 6// structured aggregate AT session boundary so the operator can read 7// "what happened this session" in one shot. 8// 9// Composes: 10// nx_hunter -- kills + active + by-pillar counts 11// nx_gatherer -- yields + by-kind counts 12// nx_audit_dashboard -- attention indicators 13// nx_book -- entries added this session 14// nx_provenance_chain -- transforms run this session 15// nx_session_imbalance_q10 (gather_journal) -- hunt/gather balance 16 17import "nx_syscalls.nx" 18import "nx_tier.nx" 19 20const NX_SS_OK: nx_int = 0 21const NX_SS_ERR_NULL: nx_int = 1 22 23// ===== Sealed enum: NxSessionVerdict ============================== 24 25const NX_SV_VALID: nx_int = 0 // hunt + gather both non-zero 26const NX_SV_NON_SESSION: nx_int = 1 // missing one or both axes 27const NX_SV_IMBALANCED: nx_int = 2 // ratio outside band 28const NX_SV_COMPROMISED: nx_int = 3 // colony hit RED during session 29const NX_SV_N_VERDICTS: nx_int = 4 30 31// ===== Struct: NxSessionSummary =================================== 32 33struct NxSessionSummary { 34 started_us: nx_size, 35 ended_us: nx_size, 36 hunt_kills: nx_int, 37 hunt_active: nx_int, 38 gather_fields_sown: nx_int, 39 gather_total_ms: nx_size, 40 book_entries_added: nx_int, 41 book_alerts: nx_int, 42 imbalance_q10: nx_int, 43 verdict: nx_int, 44} 45 46func nx_sv_is_valid(v: nx_int) -> nx_int { 47 if v < 0 { return 0 } 48 if v >= NX_SV_N_VERDICTS { return 0 } 49 return 1 50} 51 52func nx_session_summary_new(started_us: nx_size) -> *NxSessionSummary { 53 let s: *NxSessionSummary = (sys_mmap(80)) as *NxSessionSummary 54 s.started_us = started_us 55 s.ended_us = 0 56 s.hunt_kills = 0 57 s.hunt_active = 0 58 s.gather_fields_sown = 0 59 s.gather_total_ms = 0 60 s.book_entries_added = 0 61 s.book_alerts = 0 62 s.imbalance_q10 = 512 63 s.verdict = NX_SV_NON_SESSION 64 return s 65} 66 67// Caller-driven setters 68 69func nx_session_summary_set_hunt(s: *NxSessionSummary, kills: nx_int, active: nx_int) -> nx_int { 70 s.hunt_kills = kills 71 s.hunt_active = active 72 return NX_SS_OK 73} 74 75func nx_session_summary_set_gather(s: *NxSessionSummary, fields: nx_int, total_ms: nx_size) -> nx_int { 76 s.gather_fields_sown = fields 77 s.gather_total_ms = total_ms 78 return NX_SS_OK 79} 80 81func nx_session_summary_set_book(s: *NxSessionSummary, entries: nx_int, alerts: nx_int) -> nx_int { 82 s.book_entries_added = entries 83 s.book_alerts = alerts 84 return NX_SS_OK 85} 86 87func nx_session_summary_set_imbalance(s: *NxSessionSummary, q10: nx_int) -> nx_int { 88 s.imbalance_q10 = q10 89 return NX_SS_OK 90} 91 92// ===== nx_session_summary_finalize ================================ 93// 94// Compute verdict from the populated counters. Called at session 95// end. Verdict rules: 96// - hunt_kills==0 OR gather_fields==0 -> NON_SESSION 97// - imbalance outside [171, 853] -> IMBALANCED 98// - book_alerts > 0 AND severity == ALERT in book -> COMPROMISED 99// - else VALID 100 101func nx_session_summary_finalize(s: *NxSessionSummary, 102 ended_us: nx_size, 103 any_compromised_alert: nx_int) -> nx_int { 104 s.ended_us = ended_us 105 if s.hunt_kills == 0 { 106 s.verdict = NX_SV_NON_SESSION 107 return NX_SS_OK 108 } 109 if s.gather_fields_sown == 0 { 110 s.verdict = NX_SV_NON_SESSION 111 return NX_SS_OK 112 } 113 if any_compromised_alert == 1 { 114 s.verdict = NX_SV_COMPROMISED 115 return NX_SS_OK 116 } 117 if s.imbalance_q10 < 171 { 118 s.verdict = NX_SV_IMBALANCED 119 return NX_SS_OK 120 } 121 if s.imbalance_q10 > 853 { 122 s.verdict = NX_SV_IMBALANCED 123 return NX_SS_OK 124 } 125 s.verdict = NX_SV_VALID 126 return NX_SS_OK 127} 128 129func nx_session_summary_verdict(s: *NxSessionSummary) -> nx_int { 130 return s.verdict 131} 132 133func nx_session_summary_duration_us(s: *NxSessionSummary) -> nx_size { 134 if s.ended_us == 0 { return 0 } 135 if s.ended_us < s.started_us { return 0 } 136 return s.ended_us - s.started_us 137} 138 139func nx_session_summary_is_valid(s: *NxSessionSummary) -> nx_int { 140 if s.verdict == NX_SV_VALID { return 1 } 141 return 0 142}