Implements the requested cube service: a long-lived daemon that holds ONE CubeStore for its whole lifetime and serves the cube command language over a Unix-domain socket, plus cubec to talk to it. - cubesys::commands: factored the single command interpreter (Session::exec) so cube REPL, cubec client, and the daemon run identical logic - cubesys::net: dependency-free length-framed AF_UNIX transport - cubesys::persist: dependency-free JSON snapshot (atomic tmp+rename) so the store -- including sealed/encrypted records -- survives daemon restarts - cube-server: listens on $XDG_RUNTIME_DIR/cube/cube.sock, snapshots to $XDG_STATE_HOME/cube/cube-store.json, replays on startup - cubec: one-shot + REPL client over the socket - cube.rs trimmed to a thin REPL/script/demo driver (help text updated) - /etc/systemd/system/cube.service: runs as luulu, ProtectSystem=strict, RestrictAddressFamilies=AF_UNIX, Restart=on-failure; enabled + active - integration.md documents the daemon + caveat (open rewrites plaintext) Verified: ./check (fmt+tests+clippy -D warnings) green; ./check mount (27 FUSE e2e) green; socket CLI round-trips; sealed record survived a full service restart and reopened+r with original value.
139 lines
6.9 KiB
Markdown
139 lines
6.9 KiB
Markdown
# CUBELinux-2 system integration (`cubesys`)
|
||
|
||
This crate is the composition layer the PDF implies but does not name as a
|
||
"Package 6". It binds Packages 3-5 (cubefs, cubecode, cubecrypt) into one
|
||
running system over a **single shared `CubeStore`**, which is the literal
|
||
CUBELinux premise: *data addressed by coordinate, not path.*
|
||
|
||
## The one invariant
|
||
|
||
> The cube is the single source of truth. cubefs is a *view* of it, cubecode
|
||
> is a *kind* of record in it, cubecrypt is a *transform* applied to records
|
||
> in it. None of them own storage.
|
||
|
||
That is what makes the integration real instead of three crates side by side:
|
||
every layer operates on the same backend, so a write through one is visible to
|
||
the others at the same coordinate.
|
||
|
||
## What binds to what
|
||
|
||
| Layer | Role in the system | Shares via |
|
||
|--------------|------------------------------------------------------|----------------------------------|
|
||
| `cubefs` | POSIX namespace; a path `C/Z/Y/X` is a coordinate | `CubeStore`, `path_to_czyx` |
|
||
| `cubecode` | code cells stored *as records*; VM runs them | `CubeStore`, `CodeCell` |
|
||
| `cubecrypt` | seals records under a `CubeEnv`; key in Null cubes | `CubeStore`, `CubeEnv` |
|
||
|
||
Two primitives in this crate do the binding:
|
||
|
||
* `path_to_czyx(path)` — the single bijection (re-exported from `cubefs`) so
|
||
path and coordinate never disagree.
|
||
* `load_code_cell(store, path)` — reads the record at a path's coordinate and
|
||
decodes it as cubevm bytecode. A cubefs file **is** a runnable VM cell.
|
||
|
||
## End-to-end properties proved by the tests
|
||
|
||
1. **A path is a function.** Writing bytecode at `/c012/z001/y001/x021` makes
|
||
cubefs list it as a file *and* the VM run it at `Czyx::new(12,1,1,21)` —
|
||
same coordinate, two views. (Note: an axis value of `0` is Null control
|
||
space and is rejected as a path component, so every user path maps to a
|
||
coordinate with all axes in `1..=255`; keys and metadata live in Null
|
||
space and are addressed directly by `C.Z.Y.X`.)
|
||
2. **Seal then run is the same record.** `CubeEnv::put_encrypted` at a
|
||
coordinate leaves the cube carrying an encrypted body (encrypted flag set);
|
||
the raw body no longer decodes as bytecode. To *execute* a sealed record the
|
||
system decrypts it back into a plaintext record at the same coordinate, then
|
||
runs the VM (the VM runs code located by coordinate; reading the envelope
|
||
directly would fail decode). `open` + `CodeCell::from_record` + `Vm::run`
|
||
recovers and executes it on the *same* store.
|
||
|
||
## Bugs found and fixed while integrating (real, cross-crate)
|
||
|
||
Building the integration turned up two latent cross-crate bugs that the
|
||
per-package test suites never exercised:
|
||
|
||
* **`CubeHeader::refresh_flags()` dropped out-of-band flag bits.** It recomputed
|
||
`flags` only from structured fields (bits 0–7), so `cubecrypt`'s
|
||
`HEADER_FLAG_ENCRYPTED` (bit 12), which is set out-of-band, was wiped on every
|
||
refresh. `put_encrypted` calls `refresh_flags` before storing, so an encrypted
|
||
record lost its flag. Fixed by preserving spare bits (8..=15) in
|
||
`refresh_flags`. (`cubecoords`)
|
||
* **The record codec never serialized `flags`.** `record_codec::encode_header`
|
||
wrote only structured fields, and `decode_header` rebuilt `flags` from them —
|
||
so even a correctly-set encrypted bit was lost on the store round-trip. Added
|
||
tag `12` (raw flag bits) to the TLV codec so out-of-band bits survive.
|
||
(`cubestore`)
|
||
|
||
Both are now guarded by `cubesys` integration tests.
|
||
|
||
## Why this, and not a "Package 6"
|
||
|
||
The PDF arc ends at cubecrypt; the only named next package is `cubeai`, which
|
||
the directive explicitly excluded (hardware cannot run it). Integration is the
|
||
honest completion: it turns five isolated crates into the coherent
|
||
coordinate-addressed system the design describes, without inventing a package
|
||
the spec never defined.
|
||
|
||
## Binaries
|
||
|
||
* `cube-demo` — self-contained tour (write two linked cells, run; seal a
|
||
record, reopen + run). Prints evidence at each step.
|
||
* `cube` — CLI: `write`, `run`, `ls`, `stat`, `seal`, `open` over one store,
|
||
interactively (`repl`), from a file (`script`), or as the demo (`demo`).
|
||
* `cube-server` — long-lived daemon: holds ONE `CubeStore` for its whole
|
||
lifetime and serves the same command language over a Unix-domain socket.
|
||
Snapshots the store to a JSON file on every request so the cube survives
|
||
restarts (see below).
|
||
* `cubec` — client for `cube-server`: one-shot (`cubec prog ...`) or REPL
|
||
(`cubec` with stdin), talking the framed Unix socket.
|
||
|
||
## The daemon: `cube-server` + `cubec`
|
||
|
||
The directive asked for "a cube systemd service that holds the store and
|
||
exposes the CLI over a socket." That is `cube-server` + `cubec`:
|
||
|
||
* **One store, one process.** The daemon owns a single [`Session`] (one
|
||
`CubeStore`) and serves requests sequentially. Every front-end — `cube`,
|
||
`cubec`, the daemon — executes the *identical* command interpreter
|
||
(`cubesys::commands::Session::exec`), so behavior cannot drift.
|
||
* **Socket transport** (`cubesys::net`): each request is one frame
|
||
(`[u32 len][utf8 command line]`), one reply frame. No delimiters, no partial
|
||
reads. Default socket is `$XDG_RUNTIME_DIR/cube/cube.sock`.
|
||
* **Persistence** (`cubesys::persist`): every record is serialized to a JSON
|
||
snapshot (`$XDG_STATE_HOME/cube/cube-store.json`) after each mutating
|
||
command (atomic temp-write + rename). On startup the daemon replays the
|
||
snapshot so sealed/encrypted state survives a restart. Verified: a record
|
||
sealed, then the service restarted, then `open`+run still halts with the
|
||
original value.
|
||
* **systemd unit** (`/etc/systemd/system/cube.service`): runs as `luulu`,
|
||
`Restart=on-failure`, `ProtectSystem=strict`, `RestrictAddressFamilies=AF_UNIX`
|
||
(socket only), writes confined to `/home/luulu/.cubelinux` and the runtime dir.
|
||
Enabled + running.
|
||
|
||
```sh
|
||
# daemon already running via systemd; talk to it:
|
||
cubec prog /c005/z001/y001/x007 const 7 halt
|
||
cubec run /c005/z001/y001/x007
|
||
cubec seal /c005/z001/y001/x007 0.1.0.1 gcm
|
||
cubec open /c005/z001/y001/x007 0.1.0.1 gcm
|
||
cubec ls /
|
||
```
|
||
|
||
**Caveat (inherited from the CLI):** `open` decrypts a sealed record and writes
|
||
the plaintext back to the same coordinate before running, so the encrypted
|
||
state is replaced by plaintext after one `open`. That is the original
|
||
`cube`/`cubesys` behavior and is acceptable for a demo/control socket; a
|
||
read-only "open" (decrypt into a scratch coordinate, run, leave the sealed
|
||
record intact) would be the fix if sealed-at-rest must be preserved across reads.
|
||
|
||
## Building / testing
|
||
|
||
```sh
|
||
cd /home/CUBELinux/CUBELinux-2
|
||
./check quick # fmt + clippy -D warnings + tests (the gate)
|
||
./check mount # also builds cubefs --features mount (FUSE adapter)
|
||
cargo run -p cubesys --bin cube-demo
|
||
cargo run -p cubesys --bin cube -- repl # local in-process REPL
|
||
# daemon mode:
|
||
systemctl status cube.service
|
||
cubec --socket /run/user/1000/cube/cube.sock ls /
|