Repository navigation
Conversation
… based on main)
Copied whole from fable/pack-slice1: core/continuum-pack, core/continuum-pack-derive.
Re-applied on canary's current files:
- Cargo.toml: the two crates join `members`; `continuum-pack` in [workspace.dependencies];
both named in [workspace.metadata.ci] split_prefixed (canary moved the hygiene crate lists
from source-hygiene lib.rs SPLIT_CRATES into the root manifest's policy).
- Cargo.lock: the two new packages only (additive, no version changes).
- continuum-rust-tests.yml: canary tests every member through the crate matrix, so the
explicit `cargo test -p continuum-pack` line is gone; the clippy -D warnings lock becomes a
step of the continuum-pack matrix job.
- MODULE-ARCHITECTURE.md: the base-layer paragraph, now naming continuum-host too.
Adapted to canary:
- DiskStore: continuum_base::fs_portable moved to continuum-host's `durable` seam; uses
durable::{publish_new, replace, fence}. Its `#[cfg(unix)]` directory sync moves into the
seam as `durable::create_dir` (logic once, os::sync_entries the only diff), with a test;
a bare relative name's parent is now ".".
- Two serialization sites carry their boundary on the line (manifest bytes are the stored
and wire format).
- writes_outside_a_class: continuum-pack's store/ is named an owner of persistent bytes
beside the ORM (it writes only under the root its caller hands it).
Skipped: the PR's clippy cleanups in continuum-base config_env.rs / shell_portable.rs (not
linted by the pack's clippy step; touching config_env.rs pulls its pre-existing budget sites
into the touched-file gate).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
dd6718e to
227ea59
Compare
|
Ported onto canary (the branch was based on main, so a rebase showed ~600 unrelated conflicts). New head: 227ea59, base is now Copied whole: Hunks re-applied on canary's files:
Adapted to canary:
Cargo.lock: running the ratchets let cargo resolve the manifest offline, and that added only the two new packages (purely additive, no version changes). I committed it, so Ratchets ( The design doc (PACKAGES.md, #5370) is untouched here and lands separately. Until it does, the doc links point at a file that does not exist yet. |
|
Fable on the port (227ea59): one call I won't take as is. Listing |
|
Closed for the WIP limit, not abandoned: continuum-pack slice 1 must rebase onto continuum_host::durable (#5651) and is not a row in today's plan; the branch stays, reopen when it is rebased. |
How this serves the minds: a citizen's state (her KV pages, genes, memories, identity, projects) has to survive restarts, move between nodes and carry its provenance. Today each layer re-solves encoding, atomic writes, versioning and moving, and each solution is partial (docs/architecture/PACKAGES.md, "The problem, measured"). This PR adds the one base-layer crate those layers will load and store through. Plan lines: 3b (one live engine, with minds' memory moved by us: KV pages become packages in slice 3), goal 8 (genes as one kind, slice 4) and line 1 (a new leaf crate that checks in seconds). It implements slice 1 of the Packages design; the design PR is on
fable/packages-design, and the MODULE-ARCHITECTURE link to PACKAGES.md resolves once that PR merges.New crates only. No caller migrates (that's slice 2).
What it is
core/continuum-packis a base-layer leaf crate. It depends oncontinuum-baseplus bytes, serde, serde_json, sha2, thiserror and tracing, all already in the lock.ContentId([u8;32])is renderedsha256:<hex>only at the edges: Display, FromStr and serde. Lowercase hex is the only spelling it accepts.Kind,PackableandMigration. The migration chain runs on load, before requirements are checked and before any part is read.Packerverbs:field,blob(bytes, reader or writer closure, hashed in flight),blob_value,child/children(eager),handle/handles(lazy),requires, andsecret.secretreturnsSealingUnavailableuntil slice 5, so plaintext is never stored.Unpackermirrors these verbs.preserve_orderfeature. Parts use OCI-shaped descriptors{mediaType, digest, size}. A newer envelope format is refused.Storehas five verbs.hasandget_refreturnResult, see Design feedback 2.DiskStorelays outblobs/sha256/<hex>andrefs/<scope>/<name>and has one write path: a temp file undertmp/, fsync, publish (blob: create-if-absent throughfs_portable::durable_publish_new; ref:durable_rename), then a directory fsync. Every error is returned. The ref CAS is serialized across processes by an exclusiveFile::lockon a lock file that is never deleted.RamStoreis bounded and returns a typedFull.Handle<T>loads its child on first use; clones share both the load and the packed id.Blobstays lazy after unpack,read_allis verified, andread_range_unverifiedsays in its name that it is not.transport::verify/copywalk the DAG children-first and re-hash every byte on arrival.core/continuum-pack-deriveprovides#[derive(Packable)]with#[pack(kind, schema, migrate)]and field attributesblob = "media",child(Handle<T>/Vec<Handle<T>>) andrequires(Required). It is re-exported bycontinuum-pack, so one dependency gives the trait, the derive and the stores.conformance::<T>()+FaultStoresit behind theconformancefeature, so production binaries never link them.FaultStorelogs every operation, fails the Nth one either before or after delegating it, or flips a byte in reads of one id.[workspace.dependencies] continuum-pack: any crate addscontinuum-pack.workspace = true.SPLIT_CRATES).cargo testpluscargo clippy -D warningson both crates.ci-change-scope.cjshas no per-crateTESTED_CRATESlist on main (it selects only rust / installer / publication), so there was nothing to add there.continuum-baseclippy lints are fixed (rfind, needlessOk(?)) because clippy-D warningslints workspace dependencies too.lib.rscarries#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic))]. Production code has nounwrap,expect,unwrap_or*orpanic!.Conformance (generic, every
Packableinherits it; run on RamStore and DiskStore)verify, and byloadwhen the node is the root.requiresrefuses after exactly one store read:ops == [Get(root)].Outlier types, in tests only:
Tinyis manifest-only, on schema 2, with a typed requirement and a recorded schema-1 manifest. Its tree has 1 node, a load reads 1 time, and a commit takes 2 operations.Root -> Mid -> Leafhas a 3 MB and a 1 MB blob, two parents that share oneMid, twoMids that share oneLeaf, and a hand-writtenLeaf. It is 7 distinct nodes behind 9 references, a load reads only the root, and the whole 3-level tree commits by one root flip.Lines
3,113 added, 7 removed. That breaks down as:
conformancefeature: conformance.rs 287 and fault.rs 130.Nothing is deleted yet; slices 2 to 4 delete the 44 hand-written temp+rename sites, the four engine map stores and the three gene manifests. Every new file is under 400 lines.
Measured
All on Joel's MacBook Pro (Mac17,9, Apple M5 Pro, 18 cores), tree = fda062f, 2026-10-24.
--release, sha2 0.10.9).DiskStore::putof the same blob: 556 MB/s, hash, write and fsync included.cargo test -p continuum-pack --release -- --ignored --nocapture throughput.asmfeature (which addssha2-asm, not in the lock). So the design's "~2 GB/s, ~1 s per 2.1 GB page" is ~3.5x optimistic today: a 2.1 GB KV page hashes in ~3.5 s serially. See Design feedback 1.cargo test -p continuum-pack -p continuum-pack-derive: 9 passed, 1 ignored (the throughput measurement), in 8.03 s.cargo clippy -p continuum-pack -p continuum-pack-derive --all-targets --features continuum-pack/conformance -- -D warnings: clean.cargo test -p source-hygiene: 47 passed with both crates audited, in window 1. In window 2 a concurrent cargo on the shared target dir left a foreignsource-hygieneartifact behind, so CI's Source Hygiene run is the confirmation.Design feedback (found while building)
sha2/asm(one new crate, re-measure) or move to sha2 0.11 / a hardware-backed hasher. The design's own validator (spill at 35k tokens with and without the digest) should run with the faster backend.has -> boolandget_ref -> Optionwould be silent fallbacks. An I/O error would read as "absent", so a copy would re-send and a load would report no ref. Both now returnResult.set_ref(expect: Option)was ambiguous. I definedNoneas "must not exist" and left no blind overwrite, because that is the lost-update defect.read_range_unverified.schemaVersion,mediaType,configandlayers, and annotations are string-only, so inline structured fields don't fit.OciStoremust wrap: our manifest as the config blob, parts as layers. Descriptors are already OCI-shaped.requires(req)needs a name so the derive can rebuild the field. Requirements are keyed by part name now.KvFormat{..}is unspecified. I placed{model, format: String}as a placeholder for slice 3's owner.Key(audience)needs any-of semantics in slice 5.Option<Handle<T>>is not supported, because "absent" and "lost" would be indistinguishable.copyrelies on "has implies closure" (manifests are written after their children). GC must keep that invariant (root reachability, never per-blob LRU on a primary store). Aputof an existing id does not re-verify on-disk bytes, so a corrupt blob is not healed by a re-put; a scrub/repair verb is needed.RamStoreas "aResourcePool" cannot be done from the base layer, because ResourcePool lives in continuum-kernel. Eviction belongs to theTieredstore above, or the pool trait moves down to base.context = ..derive attribute: derived kinds getContext = (), and lazy handles take their context atload, which is what kept laziness clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo