nx_log_category.nx source
↩ module page · 236 lines · 9016 B
1// nx_log_category.nx -- sealed-enum log-field classification with
2// structural people-data refusal.
3//
4// User directive 2026-05-17: "yes we want logs for systems not
5// people and s class".
6//
7// The sealed enum makes the system/people distinction LOAD-BEARING:
8// every log call declares its category; the emitter refuses to
9// transmit PEOPLE_* categories without an explicit caller-supplied
10// override token. This is structural prevention, not policy hope.
11//
12// Sealed-enum NxLogCategory (8 states):
13//
14// SYSTEM categories (default-allowed):
15// SYSTEM_STATE daemon uptime, state transitions, version
16// SYSTEM_PERF latency p50/p95, throughput, queue depths
17// SYSTEM_ERROR errno, crash dumps, aggregate error counts
18// SYSTEM_CONFIG config loaded, env var set, capability flag
19// SYSTEM_SECURITY auth failure AGGREGATE COUNT (not per-user)
20//
21// PEOPLE categories (refused by default; require override):
22// PEOPLE_PII IP, UA, cookie, fingerprint, email, name
23// PEOPLE_BEHAVIOR per-user click path, dwell time, navigation
24// PEOPLE_LOCATION geo-IP, coarse city, precise GPS
25//
26// Per cardinal feedback-privacy-by-default-no-tracking:
27// - SYSTEM categories serve operations: capacity planning, alert
28// thresholds, crash diagnosis, performance regressions
29// - PEOPLE categories enable surveillance: subpoena targets,
30// advertising profiles, behavioral manipulation
31//
32// Substrate's structural enforcement:
33// Default emit accepts ONLY SYSTEM categories.
34// PEOPLE category emit requires an explicit override token whose
35// value carries a citation (legal requirement / regulated industry
36// carve-out / user-explicit-consent flow). The grader scans for
37// override tokens + ensures each has a justification annotation.
38//
39// nx_capability_claims:
40// needs: [sealed_enum, byte_ops]
41// provides: [log_category_enum, system_vs_people_distinction,
42// structural_people_data_refusal]
43// safety: [no_unchecked_deref, no_floating_point, no_syscall,
44// bit_equal_reproducible, default_refuses_pii]
45// verdict: [sealed_enum_9_state]
46// license: ORIGINAL
47// kind: racing_crew_specialist
48// layer: L2 (transform: log call -> structural verdict)
49
50// ---- Sealed enum: log category -----------------------------------
51
52const NXLC_SYSTEM_STATE: i64 = 0
53const NXLC_SYSTEM_PERF: i64 = 1
54const NXLC_SYSTEM_ERROR: i64 = 2
55const NXLC_SYSTEM_CONFIG: i64 = 3
56const NXLC_SYSTEM_SECURITY: i64 = 4
57const NXLC_PEOPLE_PII: i64 = 5
58const NXLC_PEOPLE_BEHAVIOR: i64 = 6
59const NXLC_PEOPLE_LOCATION: i64 = 7
60const NXLC_CATEGORY_N: i64 = 8
61
62func nxlc_category_is_valid(c: i64) -> i64 {
63 if c < 0 { return 0 }
64 if c >= NXLC_CATEGORY_N { return 0 }
65 return 1
66}
67
68func nxlc_category_name(c: i64) -> *u8 {
69 if c == NXLC_SYSTEM_STATE { return "SYSTEM_STATE" as *u8 }
70 if c == NXLC_SYSTEM_PERF { return "SYSTEM_PERF" as *u8 }
71 if c == NXLC_SYSTEM_ERROR { return "SYSTEM_ERROR" as *u8 }
72 if c == NXLC_SYSTEM_CONFIG { return "SYSTEM_CONFIG" as *u8 }
73 if c == NXLC_SYSTEM_SECURITY { return "SYSTEM_SECURITY" as *u8 }
74 if c == NXLC_PEOPLE_PII { return "PEOPLE_PII" as *u8 }
75 if c == NXLC_PEOPLE_BEHAVIOR { return "PEOPLE_BEHAVIOR" as *u8 }
76 if c == NXLC_PEOPLE_LOCATION { return "PEOPLE_LOCATION" as *u8 }
77 return "INVALID" as *u8
78}
79
80func nxlc_category_name_len(c: i64) -> i64 {
81 if c == NXLC_SYSTEM_STATE { return 12 }
82 if c == NXLC_SYSTEM_PERF { return 11 }
83 if c == NXLC_SYSTEM_ERROR { return 12 }
84 if c == NXLC_SYSTEM_CONFIG { return 13 }
85 if c == NXLC_SYSTEM_SECURITY { return 15 }
86 if c == NXLC_PEOPLE_PII { return 10 }
87 if c == NXLC_PEOPLE_BEHAVIOR { return 15 }
88 if c == NXLC_PEOPLE_LOCATION { return 15 }
89 return 7
90}
91
92// ---- Family predicates -------------------------------------------
93
94// Returns 1 if category is in the SYSTEM family (default-allowed).
95func nxlc_is_system(c: i64) -> i64 {
96 if c == NXLC_SYSTEM_STATE { return 1 }
97 if c == NXLC_SYSTEM_PERF { return 1 }
98 if c == NXLC_SYSTEM_ERROR { return 1 }
99 if c == NXLC_SYSTEM_CONFIG { return 1 }
100 if c == NXLC_SYSTEM_SECURITY { return 1 }
101 return 0
102}
103
104// Returns 1 if category is in the PEOPLE family (refused by default).
105func nxlc_is_people(c: i64) -> i64 {
106 if c == NXLC_PEOPLE_PII { return 1 }
107 if c == NXLC_PEOPLE_BEHAVIOR { return 1 }
108 if c == NXLC_PEOPLE_LOCATION { return 1 }
109 return 0
110}
111
112// ---- Sealed enum: emit verdict -----------------------------------
113
114const NXLC_OK: i64 = 0
115const NXLC_REFUSED_NO_OVERRIDE: i64 = 1
116const NXLC_BAD_CATEGORY: i64 = 2
117const NXLC_BAD_OVERRIDE: i64 = 3
118const NXLC_BAD_ARG: i64 = 4
119const NXLC_VERDICT_N: i64 = 5
120
121func nxlc_verdict_is_valid(v: i64) -> i64 {
122 if v < 0 { return 0 }
123 if v >= NXLC_VERDICT_N { return 0 }
124 return 1
125}
126
127func nxlc_verdict_name(v: i64) -> *u8 {
128 if v == NXLC_OK { return "OK" as *u8 }
129 if v == NXLC_REFUSED_NO_OVERRIDE { return "REFUSED_NO_OVERRIDE" as *u8 }
130 if v == NXLC_BAD_CATEGORY { return "BAD_CATEGORY" as *u8 }
131 if v == NXLC_BAD_OVERRIDE { return "BAD_OVERRIDE" as *u8 }
132 if v == NXLC_BAD_ARG { return "BAD_ARG" as *u8 }
133 return "INVALID" as *u8
134}
135
136// ---- Override token validation -----------------------------------
137//
138// To emit a PEOPLE-category log, caller passes a non-null override
139// token + cite_reason. The token MUST be the literal byte string
140// "privacy-override" (16 bytes); cite_reason must be non-empty.
141//
142// This forces every people-data log call to:
143// (a) be visible to grep / static analysis
144// (b) carry an inline justification (legal cite, consent flow ID,
145// or `audit-only` annotation)
146// (c) be auditable by the racing-crew grader (queued:
147// bench/nx_privacy_override_audit.sh)
148
149func nxlc_override_is_valid(token: *u8, token_n: i64,
150 cite: *u8, cite_n: i64) -> i64 {
151 if token == (0 as *u8) { return 0 }
152 if token_n != 16 { return 0 }
153 let expected: *u8 = "privacy-override" as *u8
154 var i: i64 = 0
155 while i < 16 {
156 if token[i] != expected[i] { return 0 }
157 i = i + 1
158 }
159 if cite == (0 as *u8) { return 0 }
160 if cite_n <= 0 { return 0 }
161 return 1
162}
163
164// ---- Categorized emit gate ---------------------------------------
165//
166// Decide whether a log call carrying `category` is allowed. Returns
167// NXLC_OK if the caller can proceed to nx_log_emit_line, or a
168// non-OK verdict that the caller must propagate.
169//
170// SYSTEM categories: always OK.
171// PEOPLE categories: OK only if override_token + cite valid.
172
173func nx_log_category_gate(
174 category: i64,
175 override_token: *u8, override_n: i64,
176 cite: *u8, cite_n: i64) -> i64 {
177 if nxlc_category_is_valid(category) != 1 { return NXLC_BAD_CATEGORY }
178 if nxlc_is_system(category) == 1 { return NXLC_OK }
179 if nxlc_is_people(category) != 1 { return NXLC_BAD_CATEGORY }
180 if override_token == (0 as *u8) { return NXLC_REFUSED_NO_OVERRIDE }
181 if nxlc_override_is_valid(override_token, override_n,
182 cite, cite_n) != 1 {
183 return NXLC_BAD_OVERRIDE
184 }
185 return NXLC_OK
186}
187
188// ---- Convenience: log-call wrapper that includes the gate --------
189//
190// Sample caller pattern:
191//
192// // SYSTEM_STATE -- always OK
193// let v: i64 = nx_log_category_gate(NXLC_SYSTEM_STATE, ...)
194// if v == NXLC_OK { nx_log_emit_line(... module ... msg ...) }
195//
196// // PEOPLE_PII -- requires override
197// let v: i64 = nx_log_category_gate(
198// NXLC_PEOPLE_PII,
199// "privacy-override" as *u8, 16,
200// "GDPR Art. 6(1)(b) -- session bound, deletes after logout" as *u8,
201// 58)
202// if v == NXLC_OK {
203// // emit allowed; explicit
204// } else {
205// // refused; substrate didn't leak the field
206// }
207
208// ---- Grader-friendly helper: count override usage ---------------
209//
210// Future bench/nx_privacy_override_audit.sh scans substrate source
211// for instances of `nx_log_category_gate(NXLC_PEOPLE_*, ...)` to
212// produce a register of every people-data log call + its cite.
213// Today's substrate has ZERO such instances. Caller asserts this
214// at startup via this helper (panic-free; returns count for the
215// caller to verify).
216
217func nx_log_count_people_overrides_in_buf(buf: *u8, n: i64) -> i64 {
218 if buf == (0 as *u8) { return 0 }
219 if n < 4 { return 0 }
220 // Naive substring scan for "NXLC_PEOPLE_" (12 bytes). Caller
221 // supplies the source file as `buf` (read via sys_read_file).
222 var count: i64 = 0
223 let needle: *u8 = "NXLC_PEOPLE_" as *u8
224 var i: i64 = 0
225 while i <= n - 12 {
226 var eq: i64 = 1
227 var j: i64 = 0
228 while j < 12 {
229 if buf[i + j] != needle[j] { eq = 0; j = 12 }
230 j = j + 1
231 }
232 if eq == 1 { count = count + 1 }
233 i = i + 1
234 }
235 return count
236}