code wiki / (root) / nx_email_mailbox.nx

nx_email_mailbox.nx source

↩ module page · 152 lines · 6559 B

1// nx_email_mailbox.nx -- EMAIL RUNG R3: additive-only mailbox store. 2// 3// module: nishi-core.email.mailbox 4// depends: nishi-core.store.seg_store 5// capability: CORE_EMAIL 6// 7// Where delivered mail LIVES. Composes the sovereign segment store 8// (nx_seg_store) rather than inventing persistence -- a message is one 9// keyed record (kind=1 put, key=Message-ID, value=raw RFC 5322 bytes); 10// deletion is a kind=2 TOMBSTONE appended in a new segment, never an 11// erase. This makes the mailbox obey Cardinal 13 (additive-only, 12// soft-delete via is_current, history is sacred) for free: a deleted 13// message vanishes from the current view (ss_get returns "tombstoned") 14// but every version is still walkable via ss_scan -- exactly what an 15// audit/undelete/IMAP-history needs. 16// 17// segid is supplied by the caller (a monotonic counter in production); 18// each deliver/delete commits one new immutable segment, so concurrent 19// readers never see a torn write (ss_commit renames into place). 20// 21// license_tier: INDEPENDENT_REDERIVE 22// genealogy_id: nishi_sovereign_segment_store + maildir_semantics 23// lineage_id: nishi_email_mailbox_r3 24// 25// nx_safety_envelope: 26// intended_use: "Deliver/fetch/soft-delete mail over the 27// sovereign segment store. Additive mailbox." 28// sil_target: SIL2 (lost/leaked mail) 29// evidence: [composes_nx_seg_store, byte-exact_fetch, 30// tombstone_softdelete, history_retained, 31// per-key_isolation] 32// hazard_register: [bug-tape-mail-loss, bug-tape-delete-erases-history] 33// residual_risk: "Monotonic segid allocation + folder hierarchy 34// (INBOX/Sent/...) are upstream concerns; this is 35// the single-mailbox record substrate." 36// verdict: NOT_YET_EVALUATED 37 38import "nx_seg_store.nx" 39import "nx_syscalls.nx" 40 41const NX_MBOX_CAP: i64 = 260 42const NX_MBOX_KIND_PUT: i64 = 1 43const NX_MBOX_KIND_TOMB: i64 = 2 44 45// Deliver a message: append a put record + commit a new segment. 46// Returns 0 on success, negative on failure. 47func nx_mbox_deliver(prefix: *u8, msgid: *u8, raw: *u8, rlen: i64, segid: i64) -> i64 { 48 let w: *i64 = ss_begin() 49 if ss_add(w, NX_MBOX_KIND_PUT, msgid, raw, rlen) != 0 { return 0 - 1 } 50 return ss_commit(prefix, w, segid) 51} 52 53// Soft-delete: append a tombstone record + commit. History is retained. 54func nx_mbox_delete(prefix: *u8, msgid: *u8, segid: i64) -> i64 { 55 let w: *i64 = ss_begin() 56 let empty: *u8 = sys_mmap(8) 57 if ss_add(w, NX_MBOX_KIND_TOMB, msgid, empty, 0) != 0 { return 0 - 1 } 58 return ss_commit(prefix, w, segid) 59} 60 61// Fetch the current version. Returns 1 (found; *ptrout/*lenout point at 62// the value bytes), 0 (tombstoned), or -1 (never existed). 63func nx_mbox_fetch(prefix: *u8, msgid: *u8, ptrout: *i64, lenout: *i64) -> i64 { 64 return ss_get_cap(prefix, msgid, ptrout, lenout, NX_MBOX_CAP) 65} 66 67// 1 iff the message currently exists (not tombstoned / absent). 68func nx_mbox_exists(prefix: *u8, msgid: *u8) -> i64 { 69 let pp: *i64 = sys_mmap(8) as *i64 70 let ll: *i64 = sys_mmap(8) as *i64 71 if ss_get_cap(prefix, msgid, pp, ll, NX_MBOX_CAP) == 1 { return 1 } 72 return 0 73} 74 75// Number of versions retained for a Message-ID (put + tombstones) -- 76// the additive-history surface. > 1 after a delete proves nothing was 77// erased. 78func nx_mbox_history_count(prefix: *u8, msgid: *u8) -> i64 { 79 let kinds: *i64 = sys_mmap(8 * NX_MBOX_CAP) as *i64 80 let ptrs: *i64 = sys_mmap(8 * NX_MBOX_CAP) as *i64 81 let lens: *i64 = sys_mmap(8 * NX_MBOX_CAP) as *i64 82 return ss_scan(prefix, msgid, kinds, ptrs, lens) 83} 84 85// ---- multi-message mailbox (append-style, like Maildir new/) ---- 86// A mailbox holds N messages keyed "<mailbox>#<seq>"; an index record 87// (key=<mailbox>) holds the ASCII message count. ONE segment commit per 88// append stores both the message and the bumped count atomically. 89 90func mbox_itoa(v: i64, out: *u8) -> i64 { 91 if v == 0 { out[0] = 48 as u8; return 1 } 92 let t: *u8 = sys_mmap(28); var m: i64 = v; var k: i64 = 0 93 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 94 var i: i64 = 0 95 while i < k { out[i] = t[k - 1 - i]; i = i + 1 } 96 return k 97} 98func mbox_atoi(s: *u8, n: i64) -> i64 { 99 var v: i64 = 0; var i: i64 = 0 100 while i < n && (s[i] & 0xff) >= 48 && (s[i] & 0xff) <= 57 { v = v * 10 + ((s[i] & 0xff) - 48); i = i + 1 } 101 return v 102} 103// build "<mailbox>#<seq>" (null-terminated) into out; returns key length. 104func mbox_key(mailbox: *u8, seq: i64, out: *u8) -> i64 { 105 var oi: i64 = 0 106 while mailbox[oi] != (0 as u8) { out[oi] = mailbox[oi]; oi = oi + 1 } 107 out[oi] = 35 as u8; oi = oi + 1 // '#' 108 let dl: i64 = mbox_itoa(seq, (out as i64 + oi) as *u8) 109 oi = oi + dl 110 out[oi] = 0 as u8 111 return oi 112} 113 114// Number of messages in a mailbox (the index record), 0 if none. 115func nx_mbox_count(prefix: *u8, mailbox: *u8) -> i64 { 116 let pp: *i64 = sys_mmap(8) as *i64 117 let ll: *i64 = sys_mmap(8) as *i64 118 if ss_get_cap(prefix, mailbox, pp, ll, NX_MBOX_CAP) != 1 { return 0 } 119 return mbox_atoi(pp[0] as *u8, ll[0]) 120} 121 122// Append a message to a mailbox; returns its 0-based sequence number. 123func nx_mbox_append(prefix: *u8, mailbox: *u8, raw: *u8, rlen: i64, segid: i64) -> i64 { 124 let n: i64 = nx_mbox_count(prefix, mailbox) 125 let key: *u8 = sys_mmap(512) 126 mbox_key(mailbox, n, key) 127 let cnt: *u8 = sys_mmap(28) 128 let cl: i64 = mbox_itoa(n + 1, cnt) 129 let w: *i64 = ss_begin() 130 if ss_add(w, NX_MBOX_KIND_PUT, key, raw, rlen) != 0 { return 0 - 1 } 131 if ss_add(w, NX_MBOX_KIND_PUT, mailbox, cnt, cl) != 0 { return 0 - 1 } 132 if ss_commit(prefix, w, segid) != 0 { return 0 - 1 } 133 return n 134} 135 136// Fetch the idx-th message in a mailbox. 1 found / 0 tombstoned / -1 absent. 137func nx_mbox_get_n(prefix: *u8, mailbox: *u8, idx: i64, ptrout: *i64, lenout: *i64) -> i64 { 138 let key: *u8 = sys_mmap(512) 139 mbox_key(mailbox, idx, key) 140 return ss_get_cap(prefix, key, ptrout, lenout, NX_MBOX_CAP) 141} 142 143// Soft-delete the idx-th message in a mailbox (tombstone; index unchanged 144// so sequence numbers stay stable -- deleted slots read as tombstoned). 145func nx_mbox_delete_n(prefix: *u8, mailbox: *u8, idx: i64, segid: i64) -> i64 { 146 let key: *u8 = sys_mmap(512) 147 mbox_key(mailbox, idx, key) 148 let w: *i64 = ss_begin() 149 let empty: *u8 = sys_mmap(8) 150 if ss_add(w, NX_MBOX_KIND_TOMB, key, empty, 0) != 0 { return 0 - 1 } 151 return ss_commit(prefix, w, segid) 152}