code wiki / (root) / nx_emit_journal.nx

nx_emit_journal.nx source

↩ module page · 144 lines · 5209 B

1// nx_emit_journal.nx -- append-only audit log of substrate emits. 2// 3// Per user oversight directive 2026-05-14 (gap #5): every successful 4// emit must journal who/what/when/where it came from. This is the 5// foundation for the patent-check (#3), quality-gate (#6), and 6// uniqueness-ledger (#8) work that builds on top. 7// 8// One row per emit, in JSONL. No reads, no rotation, no rewrites -- 9// append-only by design. Caller picks the path (e.g., 10// nxc2/_journal/YYYY-MM-DD.jsonl). 11// 12// Row schema: 13// {"module":"nx_quad","source_id":"euclid_elements", 14// "license_class":"pd_classical","ts_ns":<i64>,"event":"emit"} 15// 16// genealogy_id: substrate_audit_journal_2026_05_14 17// lineage_id: append_only_log 18 19// nx_safety_envelope: 20// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 21// sil_target: SIL1 22// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 23// verdict: NOT_YET_EVALUATED 24 25import "nx_syscalls.nx" 26import "nx_runtime.nx" 27import "nx_tier.nx" 28import "nx_fcntl.nx" 29import "nx_clock.nx" 30import "nx_str.nx" 31import "nx_strconv.nx" 32 33const NX_JOURNAL_OK: nx_int = 0 34const NX_JOURNAL_OPEN_FAIL: nx_int = -1 35const NX_JOURNAL_WRITE_FAIL: nx_int = -2 36const NX_JOURNAL_CLOSED: nx_int = -3 37 38const NX_JOURNAL_ROW_CAP: nx_size = 1024 39 40struct NxJournal { 41 fd: nx_fd, 42 n_appended: nx_int, 43 closed: nx_int, 44} 45 46const NX_JOURNAL_STRUCT_BYTES: nx_size = 24 47 48// Open the journal in O_CREAT|O_WRONLY|O_APPEND mode (0x441). Mode 0644. 49// If the file exists, new rows append to the end. 50func nx_journal_open(path: *u8) -> *NxJournal { 51 let raw: *u8 = sys_mmap(NX_JOURNAL_STRUCT_BYTES) 52 let j: *NxJournal = raw as *NxJournal 53 let fd: nx_fd = __syscall(SYS_OPENAT, AT_FDCWD, path as i64, 0x441, 0x1A4, 0, 0) 54 if fd < 0 { return 0 as *NxJournal } 55 j.fd = fd 56 j.n_appended = 0 57 j.closed = 0 58 return j 59} 60 61func nx_journal_close(j: *NxJournal) -> nx_int { 62 if j.closed == 1 { return NX_JOURNAL_OK } 63 if j.fd >= 0 { sys_close(j.fd) } 64 j.fd = -1 65 j.closed = 1 66 return NX_JOURNAL_OK 67} 68 69// Map license-class int back to a stable string for the JSON value. 70func nx_journal_lc_str(lc: nx_int, out: *u8) -> nx_size { 71 if lc == 0 { return nx_str_cpy(out, "unknown" as *u8) } 72 if lc == 1 { return nx_str_cpy(out, "forbidden" as *u8) } 73 if lc == 2 { return nx_str_cpy(out, "pd_classical" as *u8) } 74 if lc == 3 { return nx_str_cpy(out, "pd_explicit" as *u8) } 75 if lc == 4 { return nx_str_cpy(out, "pd_govdoc" as *u8) } 76 if lc == 5 { return nx_str_cpy(out, "academic_open" as *u8) } 77 if lc == 6 { return nx_str_cpy(out, "expired_patent" as *u8) } 78 return nx_str_cpy(out, "unrecognized" as *u8) 79} 80 81// Write all `n` bytes (handles partial writes); returns bytes written. 82func nx_journal_write_all(fd: nx_fd, buf: *u8, n: nx_size) -> nx_size { 83 var w: nx_size = 0 84 while w < n { 85 let p: *u8 = ((buf as nx_size) + w) as *u8 86 let r: nx_int = sys_write(fd, p, n - w) 87 if r <= 0 { return w } 88 w = w + r 89 } 90 return w 91} 92 93// Append one event row to the journal. Returns NX_JOURNAL_OK or a 94// negative status. Captures wall-clock timestamp at write time. 95func nx_journal_append( 96 j: *NxJournal, 97 module: *u8, 98 source_id: *u8, 99 license_class: nx_int, 100 event: *u8 101) -> nx_int { 102 if j.closed == 1 { return NX_JOURNAL_CLOSED } 103 let row_buf: *u8 = sys_mmap(NX_JOURNAL_ROW_CAP) 104 let lc_buf: *u8 = sys_mmap(32) 105 let ts_buf: *u8 = sys_mmap(32) 106 nx_journal_lc_str(license_class, lc_buf) 107 let ts: nx_int = nx_clock_realtime_ns() 108 let ts_len: nx_int = nx_strconv_format_i64(ts, ts_buf) 109 110 // row_buf is freshly mmap'd (zero-filled), so nx_str_cat's 111 // length-find on each call starts at 0 and advances correctly. 112 // nx_str.nx's nx_str_cat is (dst, src) -> dst; it finds the 113 // current length internally. 114 row_buf[0] = 0 115 nx_str_cat(row_buf, "{\"module\":\"" as *u8) 116 nx_str_cat(row_buf, module) 117 nx_str_cat(row_buf, "\",\"source_id\":\"" as *u8) 118 nx_str_cat(row_buf, source_id) 119 nx_str_cat(row_buf, "\",\"license_class\":\"" as *u8) 120 nx_str_cat(row_buf, lc_buf) 121 nx_str_cat(row_buf, "\",\"ts_ns\":" as *u8) 122 nx_str_cat(row_buf, ts_buf) 123 nx_str_cat(row_buf, ",\"event\":\"" as *u8) 124 nx_str_cat(row_buf, event) 125 nx_str_cat(row_buf, "\"}\n" as *u8) 126 127 let total: nx_size = nx_str_len(row_buf) as nx_size 128 let w: nx_size = nx_journal_write_all(j.fd, row_buf, total) 129 if w != total { return NX_JOURNAL_WRITE_FAIL } 130 j.n_appended = j.n_appended + 1 131 return NX_JOURNAL_OK 132} 133 134// Convenience: append an "emit" event (most common case). 135func nx_journal_emit(j: *NxJournal, module: *u8, source_id: *u8, license_class: nx_int) -> nx_int { 136 return nx_journal_append(j, module, source_id, license_class, "emit" as *u8) 137} 138 139// Convenience: append a "refused" event (gate blocked the emit). 140func nx_journal_refuse(j: *NxJournal, module: *u8, source_id: *u8, license_class: nx_int) -> nx_int { 141 return nx_journal_append(j, module, source_id, license_class, "refused" as *u8) 142} 143 144func nx_journal_n_appended(j: *NxJournal) -> nx_int { return j.n_appended }