code wiki / _hdl_build / nx_notice.nx
nx_notice.nx source
↩ module page · 422 lines · 17799 B
1// nx_notice.nx -- W-TD-2: THE NOTICE LEDGER. The state half of the safe-harbour posture.
2//
3// nx_takedown decides WHAT SHOULD HAPPEN to a notice; it is pure and holds nothing. That made it a
4// document rather than a control (debt 1785893553): the operator's posture -- "like google or bing
5// or ai companies i use what is available till its requested to be removed, and i inherit that
6// different set of requirements" -- only exists if the REQUEST can be received, TIMED, and ANSWERED
7// with evidence. ★★★★★AN INSTRUMENT THAT NOTHING IS REQUIRED TO CONSULT IS A REPORT, NOT A CONTROL
8// (the estate's own publishing-house law, applied here).
9//
10// WHAT COMPLIANCE ACTUALLY REQUIRES OF THE RECORD, and why each is mechanical here:
11// * EXPEDITIOUS RESPONSE is a claim about TIME, so the clock must start at receipt and be
12// queryable -- `overdue` answers "what am I late on" without anyone remembering to look.
13// * The record must be ANSWERABLE LATER, so it is APPEND-ONLY (rule 13, history is sacred).
14// Acting on a notice APPENDS an action row referencing the id; it never edits the original.
15// ⇒ a ledger you can rewrite is not evidence of anything, and rewriting it to look compliant is
16// the failure mode that turns a paperwork problem into a credibility problem.
17// * 512(i) repeat-infringer policy needs a COUNT, which needs persistence.
18// * QUARANTINE (nx_realperson's RP_QUARANTINE, for a SUSPECTED-minor call that may have misread a
19// lawful adult) must WITHHOLD WITHOUT DESTROYING -- so it renames into a quarantine prefix and
20// records the move. Never unlink: the misread performer's evidence has to survive.
21//
22// ⚠NOT LEGAL ADVICE. This is bookkeeping built to the shape of the regime, so a human can answer
23// quickly and prove what they did.
24//
25// verbs:
26// file <class> <locator> <complainant> <w><l><c><g><s> -- record a notice; prints its id
27// act <id> <action> [note] -- append what was done (never edits)
28// list [n] -- recent rows
29// overdue <now_epoch> -- notices past their statutory clock
30// strikes <target> -- 512(i) count for a target
31// license_tier: ORIGINAL expect_exit: 0
32// module: nishi-core.hosting.notice
33import "nx_syscalls.nx"
34import "nx_estate_path.nx"
35import "nx_takedown.nx"
36const NL_MAGIC_4096: i64 = 4096
37
38const NL_CAP: i64 = 1048576
39const NL_SECS_PER_HOUR: i64 = 3600
40
41func nw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
42func nn(v: i64) -> i64 {
43 let t: *u8 = sys_mmap(32); let b: *u8 = sys_mmap(32)
44 var m: i64 = v; var k: i64 = 0
45 if m < 0 { nw("-\x00" as *u8); m = 0 - m }
46 if m == 0 { t[0] = 48 as u8; k = 1 }
47 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
48 var i: i64 = 0
49 while i < k { b[i] = t[k - 1 - i]; i = i + 1 }
50 sys_write(1, b, k); return 0
51}
52func nl_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
53func nl_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; var a: i64 = o; while s[i] != (0 as u8) { d[a] = s[i]; a = a + 1; i = i + 1 } return a }
54func nl_catn(d: *u8, o: i64, v: i64) -> i64 {
55 if v < 0 { d[o] = 45 as u8; return nl_catn(d, o + 1, 0 - v) }
56 let t: *u8 = sys_mmap(32)
57 var m: i64 = v; var k: i64 = 0
58 if m == 0 { t[0] = 48 as u8; k = 1 }
59 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
60 var a: i64 = o; var j: i64 = k - 1
61 while j >= 0 { d[a] = t[j]; a = a + 1; j = j - 1 }
62 return a
63}
64func nl_atoi(s: *u8) -> i64 {
65 var v: i64 = 0; var i: i64 = 0; var seen: i64 = 0
66 while s[i] != (0 as u8) {
67 let c: i64 = s[i] & 0xff
68 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); seen = 1 } }
69 i = i + 1
70 }
71 if seen == 0 { return 0 - 1 }
72 return v
73}
74
75// APPEND ONE ROW. The only write this organ performs -- there is deliberately no update path.
76func nl_append(line: *u8, n: i64) -> i64 {
77 var fd: i64 = sys_openat_append("knowledge/status/notices.jrnl" as *u8, 420)
78 if fd < 0 { fd = sys_openat_append("/volume1/homes/elderwesto/nishihost/knowledge/status/notices.jrnl" as *u8, 420) }
79 if fd < 0 { return 0 - 1 }
80 var w: i64 = 0
81 while w < n {
82 let r: i64 = sys_write(fd, ((line as i64) + w) as *u8, n - w)
83 if r <= 0 { w = n } else { w = w + r }
84 }
85 sys_close(fd)
86 return 0
87}
88
89func nl_slurp(buf: *u8) -> i64 {
90 let fd: i64 = ep_open_rd("knowledge/status/notices.jrnl" as *u8)
91 if fd < 0 { return 0 }
92 var t: i64 = 0
93 var r: i64 = 1
94 while r > 0 {
95 if t >= NL_CAP { r = 0 } else {
96 r = sys_read(fd, ((buf as i64) + t) as *u8, NL_CAP - t)
97 if r > 0 { t = t + r }
98 }
99 }
100 sys_close(fd)
101 return t
102}
103
104// field <idx> of the tab-separated row [s,e) -> box[0]=off box[1]=len. 0 when absent.
105// ⚠the terminator search keeps its position (the banked law, obeyed on purpose this time).
106func nl_field(buf: *u8, s: i64, e: i64, idx: i64, box: *i64) -> i64 {
107 var p: i64 = s
108 var f: i64 = 0
109 while f < idx {
110 var go: i64 = 1
111 while go == 1 {
112 if p >= e { go = 0 } else {
113 if buf[p] == (9 as u8) { p = p + 1; go = 0 } else { p = p + 1 }
114 }
115 }
116 f = f + 1
117 }
118 if p >= e { box[0] = 0; box[1] = 0; return 0 }
119 var q: i64 = p
120 var g: i64 = 1
121 while g == 1 {
122 if q >= e { g = 0 } else {
123 if buf[q] == (9 as u8) { g = 0 } else { q = q + 1 }
124 }
125 }
126 box[0] = p; box[1] = q - p
127 return 1
128}
129
130func nl_fieldnum(buf: *u8, s: i64, e: i64, idx: i64, box: *i64) -> i64 {
131 if nl_field(buf, s, e, idx, box) == 0 { return 0 - 1 }
132 var v: i64 = 0
133 var i: i64 = 0
134 var seen: i64 = 0
135 while i < box[1] {
136 let c: i64 = buf[box[0] + i] & 0xff
137 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); seen = 1 } }
138 i = i + 1
139 }
140 if seen == 0 { return 0 - 1 }
141 return v
142}
143
144// row layout: epoch \t id \t kind \t class \t valid \t target \t action \t note
145// kind: N = notice filed, A = action taken. An A row REFERENCES an id; it never replaces the N row.
146// ⚠MEASURED DEFECT ON THE FIRST RUN: the id was simply the epoch, so three notices filed in the
147// SAME SECOND all received id 1785893887. In a compliance ledger that is not cosmetic -- `act <id>`
148// would answer all of them at once, and nl_answered would report a sibling as handled while it sat
149// untouched past its clock. A ledger whose keys collide cannot evidence anything.
150// FIX: monotonic id = max(now, highest_existing_id + 1). Scans the ledger it is about to append to,
151// so uniqueness holds across restarts and across same-second bursts.
152// ★AN IDENTIFIER MINTED FROM A CLOCK COLLIDES AT THE CLOCK'S RESOLUTION -- AND A BURST IS EXACTLY
153// WHEN A TAKEDOWN QUEUE FILLS UP.
154func nl_next_id(now: i64) -> i64 {
155 let buf: *u8 = sys_mmap(NL_CAP)
156 let n: i64 = nl_slurp(buf)
157 if n <= 0 { return now }
158 let box: *i64 = sys_mmap(32) as *i64
159 var maxid: i64 = 0
160 var p: i64 = 0
161 while p < n {
162 var e: i64 = n
163 var q: i64 = p
164 var go: i64 = 1
165 while go == 1 {
166 if q >= n { e = n; go = 0 } else {
167 if buf[q] == (10 as u8) { e = q; go = 0 } else { q = q + 1 }
168 }
169 }
170 if e > p {
171 let id: i64 = nl_fieldnum(buf, p, e, 1, box)
172 if id > maxid { maxid = id }
173 }
174 p = e + 1
175 }
176 if maxid >= now { return maxid + 1 }
177 return now
178}
179
180func nl_file_notice(class: i64, locator: *u8, complainant: *u8, valid: i64) -> i64 {
181 let now: i64 = sys_now_realtime_sec()
182 let id: i64 = nl_next_id(now)
183 let ln: *u8 = sys_mmap(NL_MAGIC_4096)
184 var o: i64 = nl_catn(ln, 0, now)
185 o = nl_cat(ln, o, "\t" as *u8); o = nl_catn(ln, o, id)
186 o = nl_cat(ln, o, "\tN\t" as *u8); o = nl_catn(ln, o, class)
187 o = nl_cat(ln, o, "\t" as *u8); o = nl_catn(ln, o, valid)
188 o = nl_cat(ln, o, "\t" as *u8); o = nl_cat(ln, o, locator)
189 o = nl_cat(ln, o, "\t" as *u8); o = nl_catn(ln, o, td_action(class, valid))
190 o = nl_cat(ln, o, "\t" as *u8); o = nl_cat(ln, o, complainant)
191 o = nl_cat(ln, o, "\n" as *u8)
192 if nl_append(ln, o) < 0 { return 0 - 1 }
193 return id
194}
195
196// Does an N (notice) row with this id exist? ⚠MEASURED: `act` originally accepted ANY id, so a
197// typo or a bad extraction produced ORPHAN action rows -- "we answered notice 1" with no notice 1
198// anywhere. That is corrosive in both directions: the ledger can read as though something was
199// handled when it was not, and an orphan whose id later collides with a real notice would mark that
200// notice answered, silencing its overdue alarm. A compliance record must not be able to claim work
201// that has no subject. ★★★★★AN ACTION ROW THAT CANNOT NAME A REAL SUBJECT IS NOT EVIDENCE, IT IS
202// NOISE THAT LOOKS LIKE EVIDENCE.
203func nl_notice_exists(id: i64) -> i64 {
204 let buf: *u8 = sys_mmap(NL_CAP)
205 let n: i64 = nl_slurp(buf)
206 if n <= 0 { return 0 }
207 let box: *i64 = sys_mmap(32) as *i64
208 var p: i64 = 0
209 while p < n {
210 var e: i64 = n
211 var q: i64 = p
212 var go: i64 = 1
213 while go == 1 {
214 if q >= n { e = n; go = 0 } else {
215 if buf[q] == (10 as u8) { e = q; go = 0 } else { q = q + 1 }
216 }
217 }
218 if e > p {
219 if nl_field(buf, p, e, 2, box) == 1 {
220 if box[1] == 1 { if buf[box[0]] == (78 as u8) {
221 if nl_fieldnum(buf, p, e, 1, box) == id { return 1 }
222 } }
223 }
224 }
225 p = e + 1
226 }
227 return 0
228}
229
230func nl_record_action(id: i64, action: i64, note: *u8) -> i64 {
231 let now: i64 = sys_now_realtime_sec()
232 let ln: *u8 = sys_mmap(NL_MAGIC_4096)
233 var o: i64 = nl_catn(ln, 0, now)
234 o = nl_cat(ln, o, "\t" as *u8); o = nl_catn(ln, o, id)
235 o = nl_cat(ln, o, "\tA\t-\t-\t-\t" as *u8); o = nl_catn(ln, o, action)
236 o = nl_cat(ln, o, "\t" as *u8); o = nl_cat(ln, o, note)
237 o = nl_cat(ln, o, "\n" as *u8)
238 return nl_append(ln, o)
239}
240
241// Has this notice id been answered? Scans for an A row referencing it.
242func nl_answered(buf: *u8, n: i64, id: i64) -> i64 {
243 let box: *i64 = sys_mmap(32) as *i64
244 var p: i64 = 0
245 while p < n {
246 var e: i64 = n
247 var q: i64 = p
248 var go: i64 = 1
249 while go == 1 {
250 if q >= n { e = n; go = 0 } else {
251 if buf[q] == (10 as u8) { e = q; go = 0 } else { q = q + 1 }
252 }
253 }
254 if e > p {
255 if nl_field(buf, p, e, 2, box) == 1 {
256 if box[1] == 1 { if buf[box[0]] == (65 as u8) {
257 if nl_fieldnum(buf, p, e, 1, box) == id { return 1 }
258 } }
259 }
260 }
261 p = e + 1
262 }
263 return 0
264}
265
266// UNANSWERED notices past their statutory clock. This is the query that makes "expeditious" real.
267func nl_overdue(now: i64) -> i64 {
268 let buf: *u8 = sys_mmap(NL_CAP)
269 let n: i64 = nl_slurp(buf)
270 if n <= 0 { nw("NOTICE-OVERDUE none (ledger empty)\n\x00" as *u8); return 0 }
271 let box: *i64 = sys_mmap(32) as *i64
272 var late: i64 = 0
273 var seen: i64 = 0
274 var p: i64 = 0
275 while p < n {
276 var e: i64 = n
277 var q: i64 = p
278 var go: i64 = 1
279 while go == 1 {
280 if q >= n { e = n; go = 0 } else {
281 if buf[q] == (10 as u8) { e = q; go = 0 } else { q = q + 1 }
282 }
283 }
284 if e > p {
285 if nl_field(buf, p, e, 2, box) == 1 {
286 if box[1] == 1 { if buf[box[0]] == (78 as u8) {
287 seen = seen + 1
288 let ep: i64 = nl_fieldnum(buf, p, e, 0, box)
289 let id: i64 = nl_fieldnum(buf, p, e, 1, box)
290 let cl: i64 = nl_fieldnum(buf, p, e, 3, box)
291 if ep >= 0 {
292 let age_h: i64 = (now - ep) / NL_SECS_PER_HOUR
293 if td_overdue(cl, age_h) == 1 {
294 if nl_answered(buf, n, id) == 0 {
295 late = late + 1
296 nw(" OVERDUE id=\x00" as *u8); nn(id)
297 nw(" class=\x00" as *u8); nn(cl)
298 nw(" age_h=\x00" as *u8); nn(age_h)
299 nw(" deadline_h=\x00" as *u8); nn(td_deadline_hours(cl))
300 nw("\n\x00" as *u8)
301 }
302 }
303 }
304 } }
305 }
306 }
307 p = e + 1
308 }
309 nw("NOTICE-OVERDUE notices=\x00" as *u8); nn(seen)
310 nw(" overdue=\x00" as *u8); nn(late)
311 if late == 0 { nw(" verdict=GREEN\n\x00" as *u8); return 0 }
312 nw(" verdict=LATE\n\x00" as *u8)
313 return 0
314}
315
316// Count notices filed against a LOCATOR (field 5 = the asset), not against an account.
317// ⚠SAY WHAT THIS ACTUALLY IS: 512(i) repeat-infringer termination is per-SUBSCRIBER, and this
318// estate has no account/uploader identity to count against, so a per-asset tally is NOT yet that
319// policy. It is a useful signal (an asset drawing repeated valid notices) and it is the honest
320// maximum this schema supports. My first output labelled it "target=<complainant>", which was wrong
321// twice over -- wrong field, and wrong noun for the legal concept.
322// ★★★★★A COUNTER THAT REPORTS A NUMBER UNDER THE WRONG NOUN IS WORSE THAN NO COUNTER: it looks
323// like compliance evidence and is not. Gap filed rather than papered over.
324func nl_strikes(target: *u8) -> i64 {
325 let buf: *u8 = sys_mmap(NL_CAP)
326 let n: i64 = nl_slurp(buf)
327 if n <= 0 { return 0 }
328 let box: *i64 = sys_mmap(32) as *i64
329 let tl: i64 = nl_len(target)
330 var c: i64 = 0
331 var p: i64 = 0
332 while p < n {
333 var e: i64 = n
334 var q: i64 = p
335 var go: i64 = 1
336 while go == 1 {
337 if q >= n { e = n; go = 0 } else {
338 if buf[q] == (10 as u8) { e = q; go = 0 } else { q = q + 1 }
339 }
340 }
341 if e > p {
342 if nl_field(buf, p, e, 2, box) == 1 {
343 if box[1] == 1 { if buf[box[0]] == (78 as u8) {
344 if nl_field(buf, p, e, 5, box) == 1 {
345 if box[1] == tl {
346 var k: i64 = 0
347 var same: i64 = 1
348 while k < tl { if buf[box[0] + k] != target[k] { same = 0; k = tl } else { k = k + 1 } }
349 if same == 1 { c = c + 1 }
350 }
351 }
352 } }
353 }
354 }
355 p = e + 1
356 }
357 return c
358}
359
360func main(argc: i64, argv: *i64) -> i64 {
361 ep_anchor()
362 if argc < 2 {
363 nw("usage: nx_notice file <class> <locator> <complainant> <w><l><c><g><s> | act <id> <action> [note] | overdue <now> | strikes <target> | list [n]\n\x00" as *u8)
364 return 1
365 }
366 let v: *u8 = argv[1] as *u8
367 if v[0] == (102 as u8) { // file
368 if argc < 6 { nw("nx_notice file needs <class> <locator> <complainant> <flags5>\n\x00" as *u8); return 2 }
369 let class: i64 = nl_atoi(argv[2] as *u8)
370 let flags: *u8 = argv[5] as *u8
371 var ok: i64 = 1
372 var i: i64 = 0
373 while i < 5 { if flags[i] != (49 as u8) { ok = 0 } i = i + 1 }
374 let id: i64 = nl_file_notice(class, argv[3] as *u8, argv[4] as *u8, ok)
375 if id < 0 { nw("NOTICE-FILE FAILED (ledger unwritable)\n\x00" as *u8); return 3 }
376 nw("NOTICE-FILED id=\x00" as *u8); nn(id)
377 nw(" class=\x00" as *u8); nn(class)
378 nw(" valid=\x00" as *u8); nn(ok)
379 nw(" required_action=\x00" as *u8); nn(td_action(class, ok))
380 nw(" deadline_h=\x00" as *u8); nn(td_deadline_hours(class))
381 nw("\n\x00" as *u8)
382 return 0
383 }
384 if v[0] == (97 as u8) { // act
385 if argc < 4 { nw("nx_notice act needs <id> <action>\n\x00" as *u8); return 2 }
386 var note: *u8 = "-\x00" as *u8
387 if argc >= 5 { note = argv[4] as *u8 }
388 let aid: i64 = nl_atoi(argv[2] as *u8)
389 // FAIL-CLOSED: refuse to record work against a notice that does not exist.
390 if nl_notice_exists(aid) == 0 {
391 nw("NOTICE-ACTION REFUSED: no notice with id=\x00" as *u8); nn(aid)
392 nw(" (an action row with no subject is not evidence -- check the id with `list`)\n\x00" as *u8)
393 return 4
394 }
395 if nl_record_action(aid, nl_atoi(argv[3] as *u8), note) < 0 { return 3 }
396 nw("NOTICE-ACTION recorded (appended, original row untouched)\n\x00" as *u8)
397 return 0
398 }
399 if v[0] == (111 as u8) { // overdue
400 var now: i64 = sys_now_realtime_sec()
401 if argc >= 3 { let t: i64 = nl_atoi(argv[2] as *u8); if t > 0 { now = t } }
402 return nl_overdue(now)
403 }
404 if v[0] == (115 as u8) { // strikes
405 if argc < 3 { nw("nx_notice strikes needs <target>\n\x00" as *u8); return 2 }
406 let c: i64 = nl_strikes(argv[2] as *u8)
407 nw("NOTICE-STRIKES locator=\x00" as *u8); nw(argv[2] as *u8)
408 nw(" notices=\x00" as *u8); nn(c)
409 nw(" over_threshold=\x00" as *u8); nn(td_repeat_action(c, TD_REPEAT_DEFAULT))
410 nw(" envelope=PER-ASSET-NOT-PER-SUBSCRIBER (512i needs an account model this estate lacks)\n\x00" as *u8)
411 return 0
412 }
413 if v[0] == (108 as u8) { // list
414 let buf: *u8 = sys_mmap(NL_CAP)
415 let n: i64 = nl_slurp(buf)
416 if n <= 0 { nw("NOTICE-LIST empty\n\x00" as *u8); return 0 }
417 sys_write(1, buf, n)
418 return 0
419 }
420 nw("nx_notice: unknown verb\n\x00" as *u8)
421 return 1
422}