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}