Skip to main content

LeanConfig

Struct LeanConfig 

Source
pub struct LeanConfig {
Show 33 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 chunk_target: usize, pub chunk_min: usize, pub chunk_max: usize, pub max_bytes: u64, pub max_files: u64, pub window_slack_secs: 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>,
}

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).

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

Checkout refusal budgets (0 = unlimited).

§max_files: u64§window_slack_secs: u64

Window deadline slack beyond the barrier start, seconds. A dead syncer’s window is ignorable past this deadline.

§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, 256 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 between the garbage collector’s HEAD of a path and its conditional DELETE, once per barrier. That gap is where another writer’s lease-free upload can land (the model’s LeanBarrierLeaseGCUnconditional), and it is two requests long, so a drill opens it on purpose. 0 = off.

§event_trace: Option<Sink>

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

Implementations§

Source§

impl LeanConfig

Source

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

Source

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

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) -> LeanConfig

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.