nx_trace_log.nx source
↩ module page · 155 lines · 5423 B
1// nx_trace_log.nx -- bit-traceability persistence layer.
2//
3// Per [[feedback-end-to-end-bit-traceability-architecture]]:
4// "Local trace per call in ~/.nishi/trace/<call_id>.jsonl default-
5// off cross-device." nx_provenance_chain is the in-memory ring;
6// nx_trace_log is the persistent log shape (in-memory in V1; on-disk
7// JSONL in V2 once nx_jsonl_writer is wired into the substrate).
8//
9// THE COMPLEMENT TO nx_provenance_chain: chain is the hot path; log
10// is the cold path. Caller appends every provenance link to the
11// chain AND emits a log entry. Chain wraps at capacity (old links
12// roll out); log grows unbounded until caller archives.
13//
14// Composes:
15// nx_provenance_chain -- the source of truth for which transforms
16// ran; trace_log is its persistent shadow
17// nx_methyl -- each entry methyl-marked
18// nx_trace_merkle -- the merkle-root summary of a log range
19// nx_trace_consent (queued) -- per-call cross-device sharing
20//
21// V1 ships:
22// - struct NxTraceEntry capturing call_id + entry_seq + transform_id
23// + params_hash + output_hash + ts + originator
24// - struct NxTraceLog as append-only growable buffer with persist_count
25// - append + count + find_by_call_id
26//
27// Gap list (V1 honest perf verdict):
28// - in-memory only; on-disk JSONL writer queued
29// - no compression of redundant transforms
30// - per-call grouping via call_id is caller-supplied (V2 auto-assigns)
31
32import "nx_syscalls.nx"
33import "nx_tier.nx"
34import "nx_provenance_chain.nx"
35
36const NX_TL_OK: nx_int = 0
37const NX_TL_ERR_FULL: nx_int = 1
38const NX_TL_ERR_BAD_ENTRY: nx_int = 2
39
40// ===== Struct: NxTraceEntry =======================================
41
42struct NxTraceEntry {
43 ts_us: nx_size,
44 call_id: nx_int,
45 entry_seq: nx_int, // monotonic within the call_id
46 transform_id: nx_int,
47 params_hash: nx_size,
48 output_hash: nx_size,
49 originator_id: nx_int,
50}
51
52// ===== Struct: NxTraceLog =========================================
53
54struct NxTraceLog {
55 entries: *NxTraceEntry,
56 capacity: nx_size,
57 head: nx_size,
58 count: nx_size,
59}
60
61const NX_TL_ENTRY_BYTES: nx_size = 56
62
63// ===== nx_trace_log_new ===========================================
64
65func nx_trace_log_new(capacity: nx_size) -> *NxTraceLog {
66 let l: *NxTraceLog = (sys_mmap(32)) as *NxTraceLog
67 let bytes: nx_size = capacity * NX_TL_ENTRY_BYTES
68 l.entries = (sys_mmap(bytes)) as *NxTraceEntry
69 l.capacity = capacity
70 l.head = 0
71 l.count = 0
72 return l
73}
74
75func _tl_at(l: *NxTraceLog, idx: nx_size) -> *NxTraceEntry {
76 return (l.entries as i64 + (idx as i64) * NX_TL_ENTRY_BYTES) as *NxTraceEntry
77}
78
79// ===== nx_trace_log_append =======================================
80//
81// Append one entry. Returns OK or FULL when capacity reached.
82// Unlike the provenance_chain ring, trace_log does NOT wrap --
83// caller is responsible for archiving + draining old entries.
84
85func nx_trace_log_append(l: *NxTraceLog,
86 ts_us: nx_size,
87 call_id: nx_int,
88 entry_seq: nx_int,
89 transform_id: nx_int,
90 params_hash: nx_size,
91 output_hash: nx_size,
92 originator_id: nx_int) -> nx_int {
93 if l.count >= l.capacity { return NX_TL_ERR_FULL }
94 let e: *NxTraceEntry = _tl_at(l, l.count)
95 e.ts_us = ts_us
96 e.call_id = call_id
97 e.entry_seq = entry_seq
98 e.transform_id = transform_id
99 e.params_hash = params_hash
100 e.output_hash = output_hash
101 e.originator_id = originator_id
102 l.count = l.count + 1
103 return NX_TL_OK
104}
105
106// ===== nx_trace_log_append_from_chain =============================
107//
108// Convenience: copy every link from a provenance_chain into the log,
109// tagged with the caller-supplied call_id. Each link becomes one
110// entry; entry_seq is the link's position in the chain.
111
112func nx_trace_log_append_from_chain(l: *NxTraceLog,
113 c: *NxProvenanceChain,
114 call_id: nx_int) -> nx_int {
115 var live: nx_size = c.count
116 if live > c.capacity { live = c.capacity }
117 var i: nx_size = 0
118 while i < live {
119 let lnk: *NxProvenanceLink = nx_provenance_link_at(c, i)
120 if (lnk as i64) != 0 {
121 let rc: nx_int = nx_trace_log_append(l, lnk.ts_us,
122 call_id, i as i64,
123 lnk.transform_id, lnk.params_hash,
124 lnk.output_hash, lnk.originator_id)
125 if rc != NX_TL_OK { return rc }
126 }
127 i = i + 1
128 }
129 return NX_TL_OK
130}
131
132// ===== nx_trace_log_count_for_call ===============================
133//
134// How many entries does this trace_log hold for the given call_id?
135// Used by trace-merkle to bound the range it summarizes.
136
137func nx_trace_log_count_for_call(l: *NxTraceLog, call_id: nx_int) -> nx_int {
138 var hits: nx_int = 0
139 var i: nx_size = 0
140 while i < l.count {
141 let e: *NxTraceEntry = _tl_at(l, i)
142 if e.call_id == call_id { hits = hits + 1 }
143 i = i + 1
144 }
145 return hits
146}
147
148func nx_trace_log_entry_at(l: *NxTraceLog, idx: nx_size) -> *NxTraceEntry {
149 if idx >= l.count { return (0 as i64) as *NxTraceEntry }
150 return _tl_at(l, idx)
151}
152
153func nx_trace_log_count(l: *NxTraceLog) -> nx_size {
154 return l.count
155}