Skip to main content

LeanConfig

Struct LeanConfig 

Source
pub struct LeanConfig {
Show 36 fields pub prefix: String, pub root: PathBuf, pub floor_secs: u64, pub sole_writer: bool, pub access: Access, pub whole_put_max: u64, pub chunked: bool, pub orphan_grace_secs: u64, pub retire_grace_secs: u64, pub chunk_target: usize, pub chunk_min: usize, pub chunk_max: usize, pub max_bytes: u64, pub max_files: u64, pub fanout: usize, pub upload_fanout: usize, pub project_id: Option<String>, pub fetch_inflight_max_bytes: u64, pub fetch_drivers: usize, pub range_get_min_bytes: u64, pub range_get_chunk_bytes: u64, pub range_get_parallelism: usize, pub sentinel_mode: SentinelMode, pub sentinel_min_interval_secs: u64, pub sentinel_hourly_budget: u64, pub sentinel_poll_secs: u64, pub claim_poll_secs: u64, pub claim_poll_head_ms: u64, pub claim_quiet_spacing_secs: u64, pub claim_deadline_secs: u64, pub drill_hold_commit_secs: u64, pub drill_hold_gc_secs: u64, pub event_trace: Option<Sink>, pub untracked_sweep_secs: u64, pub untracked_grace_secs: u64, pub renew_within_secs: u64,
}

Fields§

§prefix: String

Bucket key prefix for this subtree (project- or subtree-scoped; the proxy’s tenancy boundary).

§root: PathBuf

Workspace root (the agent’s tree).

§floor_secs: u64

Publish cadence floor, seconds (the durability contract).

§sole_writer: bool

Stamp sole_writer on every manifest this syncer installs.

Set for a workspace that is PUBLISHED rather than worked in — forge’s legible export is the shipped case. It tells every later reader that an object off its citation was moved by a stranger, so the reader refuses instead of adopting.

§access: Access

Read or read-write (Access). Default read-write.

§whole_put_max: u64

Whole-object ceiling; larger files use multipart compose.

§chunked: bool

Entries per manifest chunk, in expectation, and the floor and ceiling around it (chunked design §3). Config rather than constants because §4 already anticipates this moving: the pointer body is O(entries / target), and past ~10M entries the target is one of the two things that can be changed instead of adding a second level. Not surfaced on the CRD — nothing has needed to tune it per workspace yet, and a knob with no caller is a support burden. Publish the manifest as a CHUNK LIST rather than one generation object per citation (chunked design). Default ON since 2026-09-04.

The migration is FAIL-CLOSED and one-way per workspace: a pointer-era binary cannot parse a chunked pointer, which is the point — every layout change has to make an old reader REFUSE rather than conclude the project is empty and re-seed over it.

Small projects are not penalised, which is what made turning it on unconditional rather than threshold-gated: under the target a project is ONE chunk, so it costs a pointer plus one object and two GETs to read — exactly what the single-generation layout already cost. The indirection was paid for by step one.

false is still a supported READ path forever (a workspace that has not published since the migration) and is what the single-layout tests set explicitly.

§orphan_grace_secs: u64

How long an unreferenced chunk must have sat before the reaper may take it. Config rather than a constant because the model makes it load-bearing in a workload-dependent way: it must outlive the LONGEST publish, not the longest plausible one (LeanChunkGCRacyGrace).

§retire_grace_secs: u64

RETIRE-AGE G (M1 of the 2026-09-24 simplification analysis): a handle or chunk a commit stopped referencing is collected only once its RETIREMENT is this old — not its write, which for a long-lived file or chunk is long past the moment a UI save supersedes it. A reader that loaded a document less than G ago can fetch everything it cites. 0 = collect at once (the shape before; the battery’s default). FLINT_SYNC_RETIRE_GRACE_SECS.

§chunk_target: usize§chunk_min: usize§chunk_max: usize§max_bytes: u64

Checkout refusal budgets (0 = unlimited).

§max_files: u64§fanout: usize

Bounded concurrency for uploads and checkout fetches. The 0b rig measured the sequential loops at 561-854 PUTs/s and 1,000-2,000 GETs/s; fan-out multiplies directly against those.

§upload_fanout: usize

Concurrent UPLOADS. Deliberately separate from fanout, and measured separately: the read path’s width is what the checkout rig moved, this one is what the publish rig moved. Both paths are byte-bounded, by different gates — the read side by fetch_inflight_max_bytes here, the upload side by the store’s own gate (with_upload_inflight_max_bytes, 512 MiB from the binary), which charges every whole body and every multipart part from before its read until its PUT returns. So this number sets request concurrency, not peak RSS.

§project_id: Option<String>

The project this workspace claims to be, stamped from the CR (FLINT_SYNC_PROJECT_ID). When set, lease::verify_claim refuses to run over a prefix whose claim cell names another project — the operator’s refuse-foreign, enforced on the DATA plane (audit 2026-09-03, finding 5). None = unstamped: no check, the pre-operator posture.

§fetch_inflight_max_bytes: u64

Ceiling on bytes in flight across the checkout fan-out window.

fanout bounds the number of concurrent fetches but NOT their size, and get_whole holds each whole object in RAM before it reaches disk — so peak RSS was fanout x largest object, an unbounded product of two numbers nobody sets together. A 32-wide window over 64 MiB objects is 2 GiB in a syncer that ships with no memory limit at all. Each entry takes permits proportional to its size, so small-file trees still run the full width.

§fetch_drivers: usize

Driver tasks for the checkout fan-out (see materialize). Each runs fanout / fetch_drivers fetches at a time over its own slice of the tree, so the per-request SDK work spreads over that many cores instead of one. Default: the machine’s cores, at most 8 (FLINT_SYNC_FETCH_DRIVERS / spec.fetchDrivers, 0 = auto). Measured 2026-09-12: one driver is the ~3,300 files/s plateau on i4i.xlarge whatever fanout says; four are 6,188-6,521 at fanout 256, and fanout 256 beats 128 for the first time.

§range_get_min_bytes: u64

Materialise an object this size or larger as PARALLEL RANGES rather than one whole-object GET. 0 disables it.

Defaults to 8 MiB on the measurement that the repo’s rule asked for — runcr, i4i.large spot, us-west-1, one AZ, n=3, arms interleaved per rep, RANGES not means:

6 x 1 GiB 71.33-71.88 s -> 21.24-23.96 s 4 GiB + 2k x 16 K 57.02-57.83 s -> 15.30-18.30 s 20k x 8 KiB 16.92-19.44 s -> 16.40-17.39 s

The third row is the one that justifies a THRESHOLD rather than ranging everything: every object there is under it, the ranged path never fires, and the arms agree. Before this, the shipped path lost a cold 6 GiB checkout to a plain aws s3 cp of the same prefix on the same node (21.7 s); it now ties it.

Ranging also LOWERS peak RSS for a large object — see below — so this default costs memory nothing and returns some.

NOTE the interaction with range_get_chunk_bytes: a single part is “one whole GET with extra steps” and fetch_ranged declines it, so an object in [range_get_min_bytes, range_get_chunk_bytes) passes this threshold and still takes the whole-object path. With the defaults below the EFFECTIVE threshold is therefore 16 MiB, not 8.

Two costs of get_whole are paid together and this is what removes both: one object is one TCP stream, so a multi-GiB checkpoint runs at single-connection throughput no matter how wide the fan-out; and the body is held whole in RAM before it reaches disk, which is the product fetch_inflight_max_bytes exists to bound. Ranges write at their offset as they land, so peak RSS per object becomes range_get_chunk_bytes x range_get_parallelism instead of the object’s size.

§range_get_chunk_bytes: u64

Bytes per range request. Large enough that per-request overhead is noise against S3’s first-byte latency; small enough that a retried range is cheap.

§range_get_parallelism: usize

Ranges in flight FOR ONE OBJECT. The checkout’s byte semaphore still bounds the whole window, so this multiplies concurrency only where the fan-out has run out of objects to work on — which is exactly the tail this exists to shorten.

§sentinel_mode: SentinelMode

Sentinel posture (D0.4 pre-flight override).

§sentinel_min_interval_secs: u64

A consumed sentinel arriving sooner than this after the previous sentinel-honoring barrier waits; touches inside the interval coalesce into the pending record.

§sentinel_hourly_budget: u64

Work-metered hourly cap (D3.1): a honor charges max(1, ceil(published_bytes / whole_put_max)) units, or 0 for a no-diff honor. Exhaustion defers honors to the floor tick — the workspace degrades to exactly cadence behavior.

§sentinel_poll_secs: u64

Sentinel poll cadence (env-only, not a fleet contract).

§claim_poll_secs: u64

How often a barrier waiting for the cell re-reads it.

§claim_poll_head_ms: u64

The poll cadence of the waiter at the head of the queue (lease::CLAIM_POLL_HEAD_MS).

§claim_quiet_spacing_secs: u64

Minimum spacing between two observations that COUNT toward the dead-holder (QUIET_POLLS) and abandoned-handoff thresholds. The tests set it to 0 so a deposal takes six polls, not a minute.

§claim_deadline_secs: u64

Longest a barrier waits for the cell before it is abandoned and retried at the next floor.

§drill_hold_commit_secs: u64

DRILL-ONLY (FLINT_SYNC_DRILL_HOLD_COMMIT_SECS, never stamped by the operator): sleep this long inside the commit section, right after the claim. The one straggler the per-barrier lease still has is a holder that stalls INSIDE its commit section, and that section is milliseconds long — no drill can hit it by timing, so this is how run-writers.sh opens the window. 0 = off.

§drill_hold_gc_secs: u64

DRILL-ONLY (FLINT_SYNC_DRILL_HOLD_GC_SECS, never stamped by the operator): sleep this long before the collector’s batched DELETE of what the install retired, once per barrier. Under immutable handles the collector is one unconditional delete_many with no HEAD before it, so the window a drill wants is the one between the CAS and that batch — a peer’s sweep, or its commit, running while this barrier still owes its tidy-up. 0 = off.

§event_trace: Option<Sink>

The protocol event trace (trace.rs); None = off.

§untracked_sweep_secs: u64

How often a WRITER sweeps for an upload a lost writer left at a cited key (untracked.rs, finding 10), seconds; FLINT_SYNC_UNTRACKED_SWEEP_SECS, 0 = off. Each sweep is one LIST of the files prefix plus a manifest and an inbox read.

§untracked_grace_secs: u64

How long such an object must have sat untracked before the sweep tracks it — the gateway’s UNTRACKED_GRACE_SECS, past any live writer’s claim and commit.

§renew_within_secs: u64

How old the last cell WRITE (claim or renew) may be before a delete in the commit section renews first (review 2026-09-18, C2). A deposal needs the token still for six spaced polls (60 s), so a write younger than this is proof no deposal has happened; an older one is renewed — a conditional write that a deposal has moved, so it fences. Comfortably below the threshold. Tests set 0 to collapse a stall.

Implementations§

Source§

impl LeanConfig

Source

pub fn new(prefix: &str, root: impl Into<PathBuf>) -> Self

Source

pub fn file_key(&self, path: &str) -> String

The INGRESS key of a path: where an outside writer’s aws s3 cp lands, and where every object of this workspace lived before the immutable-handles layout (design 2026-09-19, R5). Nothing this crate publishes writes it any more; the read door, the consume and the checkout fetch HANDLES.

Source

pub fn handle_key(&self, path: &str, flush: &str) -> String

A HANDLE: the one key a write lands at, files/<path>@<flush> (design 2026-09-19, R1). Nobody else ever writes it, the manifest cites it by name, and it is deleted only once no document cites it (R3). It sorts beside the bare path, so a listing of a directory shows every live version of a file together. flush is the writer’s flush id: a barrier’s uuid, or the gateway’s ui-… form (ui_flush), whose ORDER a consume can read off the name.

Source

pub fn files_prefix(&self) -> String

The files namespace this workspace’s handles and ingress objects live under, with its trailing slash — what a sweep lists.

Source

pub fn handle_parts<'a>(&self, key: &'a str) -> Option<(&'a str, &'a str)>

(path, flush) of a handle under this prefix; None for a key that is not a handle here — the bare path of an ingress object included, so a sweep can tell the two apart by the name alone. A path may itself contain @: the flush is the LAST @-suffix, and a flush is a uuid or the gateway’s ui- form, neither of which a path component would be mistaken for in the same position.

Source

pub fn manifest_key(&self) -> String

The LEGACY single-object manifest key. Still read (a workspace written before the pointer layout has one) and, once migrated, overwritten with a document that cannot parse as a manifest — see manifest::REFUSAL_DOC for why deleting it would be a data-loss shape rather than a tidy-up.

Source

pub fn current_key(&self) -> String

The pointer: the ONLY mutable metadata object. Small, CAS’d on every citation and on every takeover rotation.

Source

pub fn generation_key(&self, seq: u64, flush_uuid: &str) -> String

One immutable generation of the entries.

Zero-padded seq FIRST, so a plain lexical listing is also chronological — what the reaper and a human with mc ls both want — and the writer’s flush uuid after it, so the key is unique per WRITE rather than per generation. Two writers that reach the same seq therefore write two different objects and race only at the pointer, which is the one place a race should be decided; and a path that legitimately rewrites without bumping is not refused by a write-once key it never meant to collide with.

Source

pub fn chunk_key(&self, addr: &str) -> String

A chunk object, addressed by CONTENT rather than by generation. Two generations that share a chunk share the object, which is where the O(changed) publish actually comes from — naming chunks by seq would rewrite every one of them on every publish even when their contents were identical.

Source

pub fn inbox_key(&self) -> String

Source

pub fn epoch_key(&self) -> String

Source

pub fn claim_key(&self) -> String

The operator’s claim cell for this prefix (lean_operator:: reconcile::claim_key writes it; the syncer only READS it).

Source

pub fn conflict_key(&self, uuid: &str, path: &str) -> String

Source

pub fn draft_body_key(&self, user: &str, path: &str) -> String

A per-user DRAFT: a durable edit that has NOT been published.

Under LEAN_DIR, which is the whole safety argument: that namespace is never scanned, never checked out, never cited by a manifest, and never swept (both sweeps are prefix-scoped — manifest::sweep_generations to manifests/, sweep_chunks to chunks/). So a draft is durable in the bucket while being invisible to classify, to checkout, to every other user, and to the agent — which is exactly what “saved but not published” has to mean.

Body and meta live under DISJOINT subtrees rather than sharing one with a suffix: with drafts/<u>/<path>.meta a file legally named notes.meta would have its BODY at the key that is notes’s META, and one user’s draft would silently read as another draft’s base etag.

Source

pub fn draft_meta_key(&self, user: &str, path: &str) -> String

The draft’s base: which version of files/<path> it was edited against. Written AFTER the body, so the only crash residue is a body with no meta — an INCOMPLETE draft, which promote refuses by name rather than publishing against a base it had to guess.

Source

pub fn draft_meta_prefix(&self, user: &str) -> String

Listing prefix for one user’s draft metas (the resume view).

Source

pub fn state_dir(&self) -> PathBuf

Source

pub fn control_dir(&self) -> PathBuf

The workspace-local control namespace (D0).

Trait Implementations§

Source§

impl Clone for LeanConfig

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.