Commit Graph
14 Commits
Author SHA1 Message Date
CUBELinux-2 16cbf6c3cb feat(cubesys): Task 6 (B) — require HELLO identity, gate seal/open
Adopted recommendation (B): make owner authority a hard guarantee instead
of the non-breaking opt-in. A mutating op now requires a stamped HELLO
identity; anonymous writes are rejected. seal/open (destructive writes)
are gated the same way, and seal stamps the owner onto the encrypted record.

Design / non-breaking bridge:
- Session gains enforce_owner: bool (default false) so library/REPL/unit
  tests stay permissive — the 26 prior tests + 3 Task-6 tests are unchanged.
- owner_violation() gains require_identity: the (false) path keeps legacy
  behaviour; the (true) path rejects no-identity mutating ops.
- The daemon flips enforce_owner=true on every connection (both HELLO and
  no-HELLO branches), implementing the default --require-identity policy.
- Added --allow-anonymous escape hatch so legacy cubec/stress.sh (which
  send no HELLO) keep working; stress.sh now passes --allow-anonymous.
- Session::set_enforce_owner() accessor so the daemon (separate bin) can
  set the private field.

Verification:
- ./check quick: EXIT=0, fmt+clippy clean, 28 cubesys lib tests (added
  enforce_owner_requires_identity, seal_open_respect_owner).
- Ad-hoc daemon verifier (LE framing) against the rebuilt cube-server:
  anonymous prog/del rejected, HELLO'd owner first-claim + self-overwrite
  allowed, cross-owner overwrite rejected. ALL PASS.

Note: seal's demo key-cell crypto path (KeyCellMissing on the synthetic key
material) is a pre-existing quirk unrelated to this change; the gate fires
before crypto, so the test verifies the gate, not the crypto.
2026-08-11 14:12:42 -04:00
CUBELinux-2 abc1b56d24 feat(cubesys): Task 6 — owner enforcement on mutating commands
Stamp owner_local_user on records written via prog/write and gate the
mutating paths (prog, write, del — both live and buffered txn) so a
session may only create or overwrite a record whose owner_local_user
matches its HELLO-declared identity.

Design (logical + expedient for the whole project):
- Owner is the durable record-level CubeHeader.owner_local_user field,
  so enforcement is replay-safe and works across daemon restart.
- Enforcement is opt-in/non-breaking: gated only when the session has a
  stamped identity AND the record has an owner. First write by an owner
  claims an unowned coord; a session with no identity (tests, legacy)
  writes freely.
- COMMIT re-checks owner on each buffered op before applying, so a
  concurrent cross-owner commit between BEGIN and COMMIT is rejected
  (txn is restored for retry, not silently dropped).
- seal/open (encrypted raw put/del) left ungated for now: their headers
  are not owner-stamped yet — tracked as follow-up.

Verification:
- ./check quick: EXIT=0, fmt+clippy clean, 26 cubesys tests (added
  owner_enforcement_blocks_cross_owner_overwrite,
  owner_enforcement_allows_first_claim_and_same_owner,
  for_tenant_carries_identity).
- Ad-hoc daemon verifier over real cube-server socket (LE framing):
  cross-owner overwrite + delete rejected, same-owner + first-claim
  allowed, no-HELLO legacy writes allowed. ALL PASS.
2026-08-11 13:41:24 -04:00
CUBELinux-2 c36f64c78d fix(cubesys): make transaction commits durable + replayable
Two correctness bugs found via ad-hoc daemon verification (T5 was
compile-verified only before):

1. decode_wal dropped WalOp::Txn entries on replay: the txn encoder emits
   {"seq","op":"txn","batch"} with NO c/z/y/x fields, but decode_wal
   read c/z/y/x unconditionally -> field_u8("c") returned Err -> the
   whole entry was skipped. Committed transactions silently vanished on
   restart. Fix: branch on op=='txn' before the c/z/y/x extraction.

2. commit was not synchronously durable: append_txn only buffered to the
   WAL pending buffer; fsync happened on the 25ms group thread. A
   clean stop within that window lost the commit. Fix: commit_txn now
   calls wal.flush_pending() (fsync) before returning, so COMMIT is
   durable on return -- a real transaction boundary.

Adds unit test commit_replays_from_wal_without_checkpoint (would have
failed before fix 1). Ad-hoc verifier exercises all 3 changed paths on
the live cube-server socket.
2026-08-11 13:09:24 -04:00
CUBELinux-2 308e20852c feat(cubesys): Task 5 transactions (BEGIN/COMMIT/ROLLBACK) + HELLO identity wiring
- Session gains txn: Option<Txn> (BEGIN snapshot + buffered ops) and
  identity: Option<TenantIdentity> (owner enforcement hook for Tasks 6+).
- prog/write/del buffer into the open txn; commit_txn applies the whole
  batch under one write lock + a SINGLE WalOp::Txn entry (atomic + durable
  replay). ROLLBACK discards. Reads consult the BEGIN snapshot (isolation).
- cube-server: HELLO-resolved tenant yields Some(ts); default-tenant path
  consistent; Session built once per connection so txns span frames.
- WalEntry gains batch field; decode_wal + encode_wal handle Txn replay.
- 4 new T5 tests: buffer/commit, rollback, snapshot isolation, durable
  reopen via WAL replay. ./check quick green.
2026-08-11 12:50:54 -04:00
CUBELinux-2 6209955b46 feat(cubesys): Task 4 reader/writer sharding (Mutex -> RwLock)
ConcurrentStore.inner is now Arc<RwLock<CubeStore>>: all read paths take the
read side, all mutations + checkpoint take the write side. Readers no longer
exclude each other and overlap an active writer (verified by
concurrent_reads_dont_block_on_writer + cube-bench Task 4 section). WAL,
checkpoint, and coordinate encoding are untouched, so durability/replay is
unchanged (.check green).

Honest finding recorded in docs/task4-reader-writer-sharding.md: on this 8-core
host std RwLock removes reader-vs-reader exclusion (correct) but shows no
wall-clock speedup for short reads (cache-line bounce on one shared lock). Real
read-throughput scaling would need sharded/lock-free storage, left as a
follow-up decision rather than invented.
2026-08-11 12:20:08 -04:00
CUBELinux-2 7f18218aca fix(cubesys): HELLO ACK + ad-hoc E2E proof of multi-tenant routing (Task 3)
The wire protocol required an explicit HELLO acknowledgement frame; a
HELLO-only client would otherwise block on the next recv. Add the ACK.

Ad-hoc E2E (real built cube-server, real 4-byte LE framed Unix socket):
  - SHARED mode: a HELLO tenant and a no-HELLO client share ONE store
    (write visible across both) — backward-compatible with cubec/stress.sh.
  - PER-TENANT mode (--tenant-dir): two HELLO tenants are HARD-isolated
    (T2 and default tenant cannot see T1's record).

Verifier: /tmp/hermes-verify-driver.sh + /tmp/hermes-verify-e2e.py
2026-08-11 11:50:05 -04:00
CUBELinux-2 50c1b55f71 feat(cubesys): wire cube-server to multi-tenant registry + HELLO (Task 3)
- TenantRegistry gains a true single-store 'shared' mode so the legacy
  --store PATH invocation (and stress.sh / old cubec) keeps serving one
  global store, while still parsing HELLO frames.
- cube-server now holds an Arc<TenantRegistry>, resolves each connection to
  its tenant's TenantSession, and stamps the client identity from
  HELLO <tenant> <owner_local> [<owner_remote>].
- Add --tenant-dir DIR for real per-tenant disk isolation (opt-in).
- Add TenantIdentity + TenantRegistry::parse_hello and a shared-session
  integration test.
2026-08-11 11:26:36 -04:00
CUBELinux-2 cf10cfcf51 feat(cubesys): per-tenant durable stores on disk (Task 2)
TenantConfig::Disk opens each tenant's ConcurrentStore under its own
sanitized subdir of store_dir (path separators -> '_', no traversal escape),
reusing the durable WAL+checkpoint machinery (incl. 49698af delta-path fix).
Registry takes a config; get_or_provision returns io::Result so a disk
open failure surfaces instead of silently falling back to memory.
Tests: tenant_store_is_isolated_on_disk (separate dirs + survives reopen,
B still isolated) + tenant_id_cannot_traverse_store_dir. ./check green.
2026-08-11 10:59:47 -04:00
CUBELinux-2 f5a8a2120c feat(cubesys): tenant id + registry skeleton (Task 1)
Add TenantId (from-str, normalized, non-empty) and TenantRegistry that
provisions one isolated TenantSession per tenant behind an RwLock<HashMap>.
In-memory store only for now; disk-backed per-tenant stores + daemon wiring
land in Tasks 2-3. 4 unit tests pass, ./check green.
2026-08-11 10:53:38 -04:00
CUBELinux-2 49698af9fc fix(cubesys): delta path mismatch in ConcurrentStore checkpoint/reopen
open() derived the delta path as "${db_path}.delta" (e.g. "db3.json.delta")
while checkpoint_store() writes via db_path.with_extension("delta")
(e.g. "db3.delta"). On reopen, load_base_plus_delta therefore read a
never-written path and silently skipped the delta, so post-checkpoint
changes were lost. Use db_p.with_extension("delta") in both places.

Also drop a no-op cp_seq.max(0) (u64 >= 0 always) to clear the clippy
-W clippy::unnecessary_min_or_max lint.

Verified: ./check all green; incremental_checkpoint_delta_model,
durable_checkpoint_and_replay, wal_recovery_after_crash pass in isolation.
2026-08-11 04:04:05 -04:00
CUBELinux-2 7ccb29aa6a cubesys: durable WAL + ConcurrentStore (NDJSON, group-commit fsync, recovery log)
- Add persist.rs: std-only NDJSON snapshot of the HashBackend store
  (no serde) for the durable checkpoint + load_into_store replay.
- Add store.rs: ConcurrentStore = Mutex<HashBackend> live store + WAL
  (newline-delimited JSON, group-commit fsync, idempotent seq-numbered
  replay) + durable JSON checkpoint + bg flusher + startup replay.
- Recovery events (checkpoint failure, WAL fsync failure, WAL replay)
  are written to a recovery.ndjson you asked to keep as the written backup
  log, so any fall-back to JSON is recorded 'in writing'.
- Refactor cube-server to thread-per-connection over ConcurrentStore.
- query_doc_type / scan_prefix / linked_to / delete_raw added.

Verified: ./check (fmt, 7 unit tests, clippy -D warnings) all green;
./check stress drove 22,080 prog+run pairs (~368/s) over 60s, daemon
survived, latency prog~9us/run~13us mean.
2026-08-11 03:18:59 -04:00
cube-agent 4f9cb2e75e Add cube-bench (correctness-gated microbenchmarks) + daemon stats telemetry
- cube-bench crate: real-code-path throughput/latency over cubestore,
  cubecrypt (aes/gcm/chacha/xts), cubecode VM, and cubesys Session.
  Every section asserts correctness before timing. Wired into ./check
  as an opt-in 'bench' stage.
- cubesys Session: per-command latency histogram + per-C-namespace record
  counts, exposed via a new 'stats' command over the live socket.
- Deployed rebuilt cube-server to /home/luulu/.cubelinux/bin and
  restarted the system cube.service; verified stats live.
2026-08-11 01:15:32 -04:00
CUBELinux-2 06ea3252eb cubesys: add cube systemd daemon (cube-server) + socket client (cubec)
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.
2026-08-11 00:10:45 -04:00
CUBELinux-2 35e5183193 feat(system): bind cubefs+cubecode+cubecrypt into one running system (cubesys)
Integrates Packages 3-5 over a single shared CubeStore, the literal
CUBELinux premise (data addressed by coordinate, not path). Adds the
cubesys crate (lib + cube CLI + cube-demo) proving two end-to-end
properties: a cubefs path IS a runnable code cell at the same coordinate,
and a sealed record reopens and runs on the same store.

Two latent cross-crate bugs surfaced and fixed while integrating:
- cubecoords: refresh_flags() now preserves out-of-band flag bits
  (8..=15), so cubecrypt's HEADER_FLAG_ENCRYPTED survives refresh.
- cubestore: record codec now serializes raw flag bits (TLV tag 12) so
  the encrypted bit survives the store round-trip.

All gates green (./check, incl. cubefs --features mount).
2026-08-10 23:42:38 -04:00