code wiki / (root) / nx_trace_log.nx

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}