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