nx_chat_store.nx
buildroot/runtime/nx_chat_store.nx
about
nx_chat_store.nx -- C1 of the comms lane: durable STORE-AND-FORWARD chat history on the sovereign
seg-store plane. Contract symbol cs_store_forward == the /compare/comms C1 watch contract.
DONE-RULE (pre-declared in comms.plan before this organ existed): a message sent to an offline
member is delivered on reconnect byte-identical and in order; history survives room close; the
store is additive-only with soft-delete and REFUSES loudly at its conf-named size budget instead
of silently dropping. REFEREE: nx_chat_store_gate (the flip is the receipt, the gate is the proof).
WHY: the live room chat is nx_chat_ring -- bounded, in-memory, gone when the room closes. Its own
header declares persistence as pluggable (disk / memory / future MLS-encrypted); this organ is the
disk layer, pure COMPOSITION of incumbents, no second ruler:
nx_store_seed_lib sts_lock/sts_unlock (the shared <prefix>plock domain) + sts_seed (explicit
room creation ONLY) + sts_atoi/sts_rowkey/sts_mm
nx_seg_store ss_open_cached + ss_hget (the amplification-safe read path, seq356) +
ss_begin_cap/ss_add/ss_commit_cas (O(1) append; CAS turns a lost update
into a loud CHAT-RETRY -- backpressure, never corruption)
nx_chatmsg cm_advance -- the monotonic receipt ladder; a delivery cursor NEVER regresses
MODEL: one plane per room at <store_root>chat_<room>- .
q:<i> message row m|<seq>|<epoch_ms>|<kind>|<sender>|<hexbody> (seq == i+1, ASSERTED at read)
q:n row count (the store's own counter -- two counters of one population, free audit)
meta:bytes cumulative DECODED body bytes (the budget ruler: cumulative, not a ring)
cur:<member> per-member delivery cursor (store-and-forward: reconnect = fetch since cursor)
del:<seq> soft-delete tombstone (additive-only, rule 13; history is never rewritten)
BODIES ARE HEX-ARMORED: plane rows are newline-framed text and chat bodies are arbitrary bytes
(tomorrow: MLS ciphertext), so rows carry lowercase hex and the BUDGET COUNTS DECODED BYTES -- the
armor never distorts the policy. Byte-identity round-trip is a gate tooth, not a promise.
KINDS are nx_chat_ring's SEALED wire constants, validated as the range 1..6 BY CITATION: a
cross-import is refused by the import graph (the ring pins nx_syscalls_x86_64, the seg-store pins
nx_syscalls; importing both duplicates the syscall layer and the duplicate guard fails the build).
If the ring grows a kind, NX_CHAT_KIND_N moves and CS_KIND_MAX here must follow.
EVERY POLICY NUMBER LIVES IN THE CONF (rule 11): knowledge/comms/chatstore.conf rows
store_root| room_bytes_cliff| room_msgs_cliff| body_bytes_max|
A missing conf row REFUSES BY NAME -- no silent code default. FAIL-CLOSED: appending to an
unopened room is REFUSED (a typo must not conjure a plane -- the nx_plane_append law); open of an
existing room is REFUSED (create-only, idempotent-safe, rule 10); every refusal ANNOUNCES its
reason and writes NOTHING.
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 5 imports · 0 importers
imports: nx_syscalls.nxnx_itoa_lib.nxnx_store_seed_lib.nxnx_seg_store.nxnx_chatmsg.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 46 | const CS_EXIT_OK: i64 = 0 |
| 47 | const CS_EXIT_USAGE: i64 = 2 |
| 48 | const CS_EXIT_REFUSED: i64 = 3 |
| 49 | const CS_EXIT_RETRY: i64 = 4 |
| 50 | const CS_EXIT_CORRUPT: i64 = 5 |
| 52 | const CS_NAME_MAX: i64 = 64 // room/member/sender token cap; keeps cur:<member> inside key caps |
| 53 | const CS_PFX_CAP: i64 = 256 // store_root + chat_ + room + - + NUL |
| 54 | const CS_ROW_OVH: i64 = 128 // row framing around the hex body |
| 55 | const CS_WSLACK: i64 = 65536 // writer slack, same margin the seed lib uses (STS_WSLACK) |
| 56 | const CS_CONF_CAP: i64 = 192 // one conf value |
| 57 | const CS_KIND_MIN: i64 = 1 // NX_CHAT_KIND_TEXT (nx_chat_ring sealed kinds, by citation) |
| 58 | const CS_KIND_MAX: i64 = 6 // NX_CHAT_KIND_HANDRAISE (== NX_CHAT_KIND_N - 1) |
| 59 | const CS_KIND_SYS: i64 = 4 // NX_CHAT_KIND_SYS -- the room-open system row |
| 60 | const CS_NL: i64 = 10 |
| 61 | const CS_PIPE: i64 = 124 |
| 62 | const CS_LOW_M: i64 = 109 // ASCII m -- the row marker |
functions
| 64 | func cs_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 65 | func cs_wn(s: *u8, n: i64) -> i64 { sys_write(1, s, n); return 0 } |
| 66 | func cs_n(v: i64) -> i64 { nxi_out(v); return 0 } |
| 67 | func cs_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 68 | func cs_eq(a: *u8, b: *u8) -> i64 |
| 75 | func cs_tok_ok(s: *u8) -> i64 |
| 92 | func cs_hexe(src: *u8, n: i64, dst: *u8) -> i64 called by 1: cs_rowbuild |
| 104 | func cs_hexv(c: i64) -> i64 called by 1: cs_hexd |
| 111 | func cs_hexd(src: *u8, n: i64, dst: *u8) -> i64 |
| 126 | func cs_confs(confpath: *u8, key: *u8, out: *u8, cap: i64) -> i64 |
| 161 | func cs_confn(confpath: *u8, key: *u8) -> i64 |
| 167 | func cs_refuse_conf(key: *u8, confpath: *u8) -> i64 |
| 176 | func cs_prefix(confpath: *u8, room: *u8, out: *u8) -> i64 |
| 188 | func cs_put_key(prefix: *u8, key: *u8, val: *u8, vlen: i64) -> i64 called by 5: cs_opencs_ackcs_delcs_setretentioncs_purge calls 4: ss_begin_capss_addss_commit_casss_next_segid |
| 194 | func cs_curkey(member: *u8, out: *u8) -> i64 |
| 200 | func cs_delkey(seq: i64, out: *u8) -> i64 |
| 207 | func cs_rowseq(p: *u8, l: i64) -> i64 called by 1: cs_fetch |
| 226 | func cs_rowbuild(seq: i64, kind: i64, sender: *u8, body: *u8, blen: i64, out: *u8) -> i64 |
| 242 | func cs_open(room: *u8, opener: *u8, confpath: *u8) -> i64 |
| 295 | func cs_store_forward(room: *u8, sender: *u8, kind: i64, body: *u8, blen: i64, confpath: *u8) -> i64 |
| 402 | func cs_fetch(room: *u8, since: i64, maxn: i64, confpath: *u8) -> i64 |
| 477 | func cs_cursor(room: *u8, member: *u8, confpath: *u8) -> i64 |
| 502 | func cs_ack(room: *u8, member: *u8, seq: i64, confpath: *u8) -> i64 |
| 550 | func cs_del(room: *u8, seq: i64, actor: *u8, confpath: *u8) -> i64 |
| 587 | func cs_status(room: *u8, confpath: *u8) -> i64 |
| 644 | func cs_setretention(room: *u8, ms: i64, actor: *u8, confpath: *u8) -> i64 |
| 680 | func cs_retention(room: *u8, confpath: *u8) -> i64 |
| 780 | func cs_rowhead(p: *u8, l: i64) -> i64 called by 1: cs_purge |
| 799 | func cs_purge(room: *u8, actor: *u8, confirm: *u8, confpath: *u8) -> i64 |
| 932 | func cs_usage() -> i64 |
| 936 | func cs_confpick(argc: i64, argv: *i64, idx: i64) -> *u8 called by 1: main |
| 940 | func cs_argn(argv: *i64, idx: i64) -> i64 |
| 945 | func main(argc: i64, argv: *i64) -> i64 |