nx_gather_journal.nx source
↩ module page · 154 lines · 5310 B
1// nx_gather_journal.nx -- append-only event ring for gather events.
2//
3// Per [[feedback-hunter-gatherer-meta-primitives-outward-inward-axes]]
4// CARDINAL: "nx_gather_journal (~/.nishi/gather.jsonl)." Append-only
5// log of every gather event (field sown / yield harvested) so the
6// session-audit gate can verify gather_count >= 1 per session.
7//
8// Mirrors nx_hunt_journal structure; pairs with it for the dual-axis
9// audit predicate (valid session = hunt_count >= 1 AND gather_count >= 1).
10//
11// Composes:
12// nx_gatherer -- emits events; journal records them
13// nx_hunt_journal -- the outward-axis dual
14
15import "nx_syscalls.nx"
16import "nx_tier.nx"
17import "nx_gatherer.nx"
18import "nx_hunt_journal.nx"
19const NX_MAGIC_1024: i64 = 1024
20
21// ===== Sealed enum: NxGatherEventKind =============================
22
23const NX_GEV_SOWN: nx_int = 0
24const NX_GEV_HARVESTED: nx_int = 1
25const NX_GEV_FALLOWED: nx_int = 2 // field set aside for season
26const NX_GEV_COMPOSTED: nx_int = 3 // prior-arc knowledge re-applied
27const NX_GEV_N_KINDS: nx_int = 4
28
29// ===== Struct: NxGatherEvent ======================================
30
31struct NxGatherEvent {
32 ts_us: nx_size,
33 field_id: nx_int,
34 event_kind: nx_int,
35 field_kind: nx_int, // NX_GFK_* at moment of event
36 yield_kind: nx_int, // NX_GYK_* on HARVESTED events; NX_GYK_N_KINDS otherwise
37 yield_value: nx_size,
38}
39
40// ===== Struct: NxGatherJournalRing ================================
41
42struct NxGatherJournalRing {
43 events: *NxGatherEvent,
44 capacity: nx_size,
45 head: nx_size,
46 count: nx_size,
47}
48
49const NX_GEV_BYTES: nx_size = 48
50
51func nx_gev_kind_is_valid(k: nx_int) -> nx_int {
52 if k < 0 { return 0 }
53 if k >= NX_GEV_N_KINDS { return 0 }
54 return 1
55}
56
57func nx_gather_journal_new(capacity: nx_size) -> *NxGatherJournalRing {
58 let j: *NxGatherJournalRing = (sys_mmap(32)) as *NxGatherJournalRing
59 let bytes: nx_size = capacity * NX_GEV_BYTES
60 j.events = (sys_mmap(bytes)) as *NxGatherEvent
61 j.capacity = capacity
62 j.head = 0
63 j.count = 0
64 return j
65}
66
67func _gather_jr_at(j: *NxGatherJournalRing, idx: nx_size) -> *NxGatherEvent {
68 return (j.events as i64 + (idx as i64) * NX_GEV_BYTES) as *NxGatherEvent
69}
70
71func nx_gather_journal_log(j: *NxGatherJournalRing,
72 ts_us: nx_size,
73 field_id: nx_int,
74 event_kind: nx_int,
75 field_kind: nx_int,
76 yield_kind: nx_int,
77 yield_value: nx_size) -> nx_int {
78 if nx_gev_kind_is_valid(event_kind) == 0 { return 1 }
79 let e: *NxGatherEvent = _gather_jr_at(j, j.head)
80 e.ts_us = ts_us
81 e.field_id = field_id
82 e.event_kind = event_kind
83 e.field_kind = field_kind
84 e.yield_kind = yield_kind
85 e.yield_value = yield_value
86 j.head = j.head + 1
87 if j.head >= j.capacity { j.head = 0 }
88 j.count = j.count + 1
89 return 0
90}
91
92func nx_gather_journal_count_kind(j: *NxGatherJournalRing, kind: nx_int) -> nx_int {
93 var hits: nx_int = 0
94 var live: nx_size = j.count
95 if live > j.capacity { live = j.capacity }
96 var i: nx_size = 0
97 while i < live {
98 let e: *NxGatherEvent = _gather_jr_at(j, i)
99 if e.event_kind == kind { hits = hits + 1 }
100 i = i + 1
101 }
102 return hits
103}
104
105func nx_gather_journal_event_count(j: *NxGatherJournalRing) -> nx_size {
106 return j.count
107}
108
109// ===== nx_session_audit_valid ====================================
110//
111// THE SESSION-AUDIT GATE per cardinal: "bench/nx_session_audit.sh
112// asserts hunt_count >= 1 AND gather_count >= 1 per session."
113// Returns 1 if both journals have at least one event each, 0 otherwise.
114// Imported from the gather_journal side because both journals must be
115// passed; pair-validation lives here arbitrarily (could equally live
116// in hunt_journal).
117
118func nx_session_audit_valid(hunt: *NxHuntJournalRing,
119 gather: *NxGatherJournalRing) -> nx_int {
120 if (hunt as i64) == 0 { return 0 }
121 if (gather as i64) == 0 { return 0 }
122 if hunt.count == 0 { return 0 }
123 if gather.count == 0 { return 0 }
124 return 1
125}
126
127// ===== nx_session_imbalance_q10 ==================================
128//
129// Returns Q10 ratio of hunt/(hunt+gather). 512 = balanced, 0 = all
130// gather, 1024 = all hunt. The cardinal flags imbalance at 5x ratio
131// for 3 consecutive sessions; the 5x equivalent is Q10 ratio outside
132// [171, 853] (1:5 or 5:1).
133
134func nx_session_imbalance_q10(hunt: *NxHuntJournalRing,
135 gather: *NxGatherJournalRing) -> nx_int {
136 let h: nx_size = hunt.count
137 let g: nx_size = gather.count
138 let total: nx_size = h + g
139 if total == 0 { return 512 }
140 return ((h as i64) * NX_MAGIC_1024) / (total as i64)
141}
142
143// ===== nx_session_is_imbalanced ===================================
144//
145// Predicate: is this session outside the 5x ratio band?
146// q10 < 171 means gather >> hunt; q10 > 853 means hunt >> gather.
147
148func nx_session_is_imbalanced(hunt: *NxHuntJournalRing,
149 gather: *NxGatherJournalRing) -> nx_int {
150 let q10: nx_int = nx_session_imbalance_q10(hunt, gather)
151 if q10 < 171 { return 1 }
152 if q10 > 853 { return 1 }
153 return 0
154}