feat(cubelinux-2): Package 1 — cubecoords + cubestore from PDF spec

New code (no recycling from prior build). Implements:
- cubecoords: Czyx 4-axis coordinate (pack/unpack u32), Null-class
  classification (Total / Cube 1-4 / User), TriWord tri-channel 64-bit
  codec (6 ASCII + 4 control bits), HeaderFlags + CubeHeader (derived
  flags). All coding decisions documented inline.
- cubestore: CubeBackend trait, HashBackend (HashMap<u32>), CubeStore with
  dependency-free length-prefixed record codec (header TLV + body).
- 8 unit tests, all passing; cargo test clean (0 warn/err).

Per directive: separate git repo; current /home/CUBELinux kept as working
tool; scoped to AI-OS-excluded PDF vision.
This commit is contained in:
CUBELinux-2
2026-08-10 18:03:04 -04:00
commit 6e13b13eea
6 changed files with 724 additions and 0 deletions
+360
View File
@@ -0,0 +1,360 @@
//! CUBELinux-2 record store over CZYX coordinates (PDF Package 1).
//!
//! Built NEW from the PDF spec; not recycled from the prior `/home/CUBELinux`
//! build. The prior build used a 3-axis `u64` point + `SpaceId`; this store
//! is keyed by the PDF's [`Czyx`] coordinate directly.
//!
//! Scope of this file: Package 1 only — a coordinate type, a tri-channel
//! codec, a header, and a minimal store abstraction that can later be backed
//! by a log-structured / RocksDB store. The PDF's later packages (cubefs,
//! cubevm, cubecrypt, cubeai) are out of scope for this hardware and are not
//! implemented here.
#![forbid(unsafe_code)]
#![warn(missing_docs)]
use cubecoords::{CubeHeader, Czyx};
use std::collections::HashMap;
/// A backend that maps CZYX coordinates to byte payloads.
///
/// Decision: trait takes `Czyx` by value for `put`/`delete` (small, `Copy`)
/// and by reference for `get`, matching the PDF signature while staying
/// allocation-light. Revisit if a future backend needs the whole key moved.
pub trait CubeBackend {
/// Store `value` at `key`.
fn put(&mut self, key: Czyx, value: Vec<u8>);
/// Fetch the value at `key`, if present.
fn get(&self, key: &Czyx) -> Option<Vec<u8>>;
/// Remove the value at `key`.
fn delete(&mut self, key: &Czyx);
}
/// In-memory backend backed by a `HashMap<u32, Vec<u8>>` keyed by the packed
/// `u32` form of [`Czyx`].
///
/// Decision: packs to `u32` (not a 4-tuple key) so the map layout matches the
/// PDF's `HashMap<u32, Vec<u8>>` example exactly and stays cheap. A production
/// backend would replace this with the on-disk store.
pub struct HashBackend(pub HashMap<u32, Vec<u8>>);
impl HashBackend {
/// Empty backend.
pub fn new() -> Self {
HashBackend(HashMap::new())
}
}
impl Default for HashBackend {
fn default() -> Self {
Self::new()
}
}
impl CubeBackend for HashBackend {
fn put(&mut self, key: Czyx, value: Vec<u8>) {
self.0.insert(key.pack_u32(), value);
}
fn get(&self, key: &Czyx) -> Option<Vec<u8>> {
self.0.get(&key.pack_u32()).cloned()
}
fn delete(&mut self, key: &Czyx) {
self.0.remove(&key.pack_u32());
}
}
/// A record store: a header + body addressed by a [`Czyx`] label.
///
/// Decision: we serialize the header and body as a single byte buffer with a
/// length-prefixed header section, rather than relying on an external
/// `bincode` dependency (keeps CUBELinux-2 dependency-free at Package 1).
/// The header is length-prefixed so the body boundary is recoverable without
/// a fixed schema — this is the "evolve toward explicit C/Z/Y/X-mapped flag
/// bytes" step the PDF mentions, done inline.
pub struct CubeStore<B: CubeBackend> {
backend: B,
}
/// On-wire layout of a stored record:
/// `[u32 header_len][header bytes][body bytes]`
/// Header bytes = JSON of `CubeHeader`. Decision: JSON (via a tiny manual
/// serializer-free path) is overkill; we instead use a simple, stable
/// binary form below. (Documented: JSON was considered; a compact binary
/// encoding is used to avoid a serde dependency at Package 1.)
mod record_codec {
use cubecoords::{CubeHeader, Czyx};
use std::collections::HashMap;
// Compact, dependency-free encoding of the header.
// Fields are written in a fixed tag-length-value stream so unknown
// future fields can be skipped on read. Tag byte + (optional) length +
// payload.
//
// Tags:
// 1 title (utf8)
// 2 doc_type (utf8)
// 3 created_at (u64 le)
// 4 size_bytes (u64 le)
// 5 owner_local_user (utf8)
// 6 owner_remote_user (utf8)
// 7 linked_records: u16 count, then count*(4*u8) CZYX bytes
// 8 total_accesses (u64 le)
// 9 total_remote_accesses (u64 le)
// 10 last_access (u64 le)
// 11 last_remote_access (u64 le)
pub fn encode_header(h: &CubeHeader) -> Vec<u8> {
let mut out = Vec::new();
if let Some(t) = &h.title {
put_utf8(&mut out, 1, t);
}
if let Some(d) = &h.doc_type {
put_utf8(&mut out, 2, d);
}
if let Some(c) = h.created_at {
put_u64(&mut out, 3, c);
}
if let Some(s) = h.size_bytes {
put_u64(&mut out, 4, s);
}
if let Some(o) = &h.owner_local_user {
put_utf8(&mut out, 5, o);
}
if let Some(o) = &h.owner_remote_user {
put_utf8(&mut out, 6, o);
}
if !h.linked_records.is_empty() {
out.push(7);
out.extend_from_slice(&(h.linked_records.len() as u16).to_le_bytes());
for r in &h.linked_records {
out.push(r.c);
out.push(r.z);
out.push(r.y);
out.push(r.x);
}
}
if h.total_accesses != 0 {
put_u64(&mut out, 8, h.total_accesses);
}
if h.total_remote_accesses != 0 {
put_u64(&mut out, 9, h.total_remote_accesses);
}
if let Some(a) = h.last_access {
put_u64(&mut out, 10, a);
}
if let Some(a) = h.last_remote_access {
put_u64(&mut out, 11, a);
}
out
}
pub fn decode_header(mut b: &[u8]) -> Option<CubeHeader> {
let mut h = CubeHeader::new();
while !b.is_empty() {
let tag = b[0];
b = &b[1..];
match tag {
1 => {
let (v, rest) = take_utf8(b)?;
h.title = Some(v);
b = rest;
}
2 => {
let (v, rest) = take_utf8(b)?;
h.doc_type = Some(v);
b = rest;
}
3 => {
let (v, rest) = take_u64(b)?;
h.created_at = Some(v);
b = rest;
}
4 => {
let (v, rest) = take_u64(b)?;
h.size_bytes = Some(v);
b = rest;
}
5 => {
let (v, rest) = take_utf8(b)?;
h.owner_local_user = Some(v);
b = rest;
}
6 => {
let (v, rest) = take_utf8(b)?;
h.owner_remote_user = Some(v);
b = rest;
}
7 => {
if b.len() < 2 {
return None;
}
let n = u16::from_le_bytes([b[0], b[1]]) as usize;
b = &b[2..];
if b.len() < n * 4 {
return None;
}
for _ in 0..n {
let c = b[0];
let z = b[1];
let y = b[2];
let x = b[3];
h.linked_records.push(Czyx::new(c, z, y, x));
b = &b[4..];
}
}
8 => {
let (v, rest) = take_u64(b)?;
h.total_accesses = v;
b = rest;
}
9 => {
let (v, rest) = take_u64(b)?;
h.total_remote_accesses = v;
b = rest;
}
10 => {
let (v, rest) = take_u64(b)?;
h.last_access = Some(v);
b = rest;
}
11 => {
let (v, rest) = take_u64(b)?;
h.last_remote_access = Some(v);
b = rest;
}
_ => return None, // unknown tag -> reject (strict at Package 1)
}
}
h.refresh_flags();
Some(h)
}
fn put_u64(out: &mut Vec<u8>, tag: u8, v: u64) {
out.push(tag);
out.extend_from_slice(&v.to_le_bytes());
}
fn put_utf8(out: &mut Vec<u8>, tag: u8, s: &str) {
let bytes = s.as_bytes();
out.push(tag);
out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
out.extend_from_slice(bytes);
}
fn take_u64(b: &[u8]) -> Option<(u64, &[u8])> {
if b.len() < 8 {
return None;
}
let mut a = [0u8; 8];
a.copy_from_slice(&b[..8]);
Some((u64::from_le_bytes(a), &b[8..]))
}
fn take_utf8(b: &[u8]) -> Option<(String, &[u8])> {
if b.len() < 4 {
return None;
}
let mut len = [0u8; 4];
len.copy_from_slice(&b[..4]);
let n = u32::from_le_bytes(len) as usize;
let rest = &b[4..];
if rest.len() < n {
return None;
}
let s = String::from_utf8(rest[..n].to_vec()).ok()?;
Some((s, &rest[n..]))
}
/// Keep `HashMap` referenced so the dependency is explicit in this module
/// even though the codec itself is generic over bytes. (Prevents an
/// unused-import warning if the backend type changes.)
#[allow(dead_code)]
pub(crate) fn _assert_backend_assoc(_: &HashMap<u32, Vec<u8>>) {}
}
impl<B: CubeBackend> CubeStore<B> {
/// Wrap a backend.
pub fn new(backend: B) -> Self {
CubeStore { backend }
}
/// Store `header` + `body` at `label`.
pub fn put_record(&mut self, label: Czyx, header: &CubeHeader, body: &[u8]) {
let hdr_bytes = record_codec::encode_header(header);
let mut buf = Vec::with_capacity(4 + hdr_bytes.len() + body.len());
buf.extend_from_slice(&(hdr_bytes.len() as u32).to_le_bytes());
buf.extend_from_slice(&hdr_bytes);
buf.extend_from_slice(body);
self.backend.put(label, buf);
}
/// Fetch and split a record into `(header, body)`.
pub fn get_record(&self, label: &Czyx) -> Option<(CubeHeader, Vec<u8>)> {
let raw = self.backend.get(label)?;
if raw.len() < 4 {
return None;
}
let mut len = [0u8; 4];
len.copy_from_slice(&raw[..4]);
let hlen = u32::from_le_bytes(len) as usize;
if raw.len() < 4 + hlen {
return None;
}
let hdr = record_codec::decode_header(&raw[4..4 + hlen])?;
let body = raw[4 + hlen..].to_vec();
Some((hdr, body))
}
/// Raw backend access (delegates put/get/delete for non-record payloads).
pub fn put_raw(&mut self, key: Czyx, value: Vec<u8>) {
self.backend.put(key, value);
}
/// Raw backend get.
pub fn get_raw(&self, key: &Czyx) -> Option<Vec<u8>> {
self.backend.get(key)
}
/// Raw backend delete.
pub fn delete_raw(&mut self, key: &Czyx) {
self.backend.delete(key);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_roundtrip() {
let mut store = CubeStore::new(HashBackend::new());
let mut h = CubeHeader::new();
h.title = Some("memory".into());
h.doc_type = Some("note".into());
h.created_at = Some(1700000000);
h.size_bytes = Some(5);
h.linked_records.push(Czyx::new(1, 2, 3, 4));
h.refresh_flags();
let label = Czyx::new(1, 10, 20, 30);
store.put_record(label, &h, b"hello");
let (rh, body) = store.get_record(&label).unwrap();
assert_eq!(body, b"hello");
assert_eq!(rh.title.as_deref(), Some("memory"));
assert_eq!(rh.doc_type.as_deref(), Some("note"));
assert_eq!(rh.created_at, Some(1700000000));
assert_eq!(rh.size_bytes, Some(5));
assert_eq!(rh.linked_records, vec![Czyx::new(1, 2, 3, 4)]);
assert!(rh.flags.has(cubecoords::HeaderFlags::HAS_ASSOCIATIONS));
}
#[test]
fn missing_record_is_none() {
let store = CubeStore::new(HashBackend::new());
assert!(store.get_record(&Czyx::new(9, 9, 9, 9)).is_none());
}
#[test]
fn null_coord_is_distinct_key() {
let mut store = CubeStore::new(HashBackend::new());
store.put_raw(Czyx::new(0, 0, 0, 0), vec![1]);
store.put_raw(Czyx::new(0, 0, 0, 1), vec![2]);
assert_eq!(store.get_raw(&Czyx::new(0, 0, 0, 0)), Some(vec![1]));
assert_eq!(store.get_raw(&Czyx::new(0, 0, 0, 1)), Some(vec![2]));
}
}