Skip to main content

Syncer

Struct Syncer 

Source
pub struct Syncer {
    pub store: Arc<dyn ObjectStore>,
    pub cfg: LeanConfig,
    pub state: SyncerState,
    pub lease: Option<EpochLease>,
    pub noted_not_regular: BTreeSet<String>,
}
Expand description

Everything a syncer operation needs: store + config + durable state.

Fields§

§store: Arc<dyn ObjectStore>§cfg: LeanConfig§state: SyncerState§lease: Option<EpochLease>

The lease held for the CURRENT commit section, if any. None between barriers: the cell arbitrates who installs a manifest and nothing else (design 2026-09-13 §4), so a barrier claims after its uploads and releases after its baseline rewrite.

§noted_not_regular: BTreeSet<String>

Standing conditions already written to conflicts.jsonl by THIS process, so a condition that persists across every poll tick is recorded once rather than per tick. Deliberately in-memory: a restart is a new incarnation and should re-record what it finds.

Implementations§

Source§

impl Syncer

Source

pub async fn consume_inbox(&mut self) -> LeanResult<Vec<InboxEntry>>

Step 1: consume the inbox. A barrier NEVER runs against an unconsumed inbox — this is what makes HITL uploads structurally un-amputatable. Returns the entries integrated (dropped from the cell at the window-open CAS).

Source

pub async fn consume_inbox_doc( &mut self, doc: &InboxDoc, ) -> LeanResult<Vec<InboxEntry>>

consume_inbox over a cell the caller has already read — the barrier reads it once and hands the same document to apply_removals, so the removal pass adds no request.

Source

pub async fn apply_removals( &mut self, doc: &InboxDoc, ) -> LeanResult<RemovalPass>

Step 1b: perform the DECLARED removals (delete/rename design §4-§6). Runs after consume_inbox_doc, so a rename’s destination is in the tree before its source leaves it — §5: create first, removal second; a partial application leaves an EXTRA file, never a missing one.

Per removal, in order:

  • the path must be containable, as a consume’s must;
  • a rename WAITS until its destination is integrated (in the baseline): a consume that deferred the destination defers the removal with it;
  • a locally-DIRTY path — unpublished edits, or a file the agent created there — is REFUSED, nothing applied, the agent’s work untouched. The same rule a consume applies to a HITL write over dirty bytes, and the reason a declared removal cannot be “just a delete”. Refused for good: the cell is told, and a retry that waited for the agent to publish would delete the very edit the refusal protected;
  • a clean file is unlinked, and the unlink is CONFIRMED by lstat before the path is declared — an unlink that did not take publishes no deletion (confirm_absences’ rule, kept);
  • a path already absent locally and known to the baseline or the merge base is declared as it stands: the agent got there first, or this tree never held it (a scoped workspace).

A declared path skips the two-scan guard (§4): that guard protects against absence INFERRED by a walk, and a declaration is not an inference. That is what lets a rename’s two halves ride one manifest generation.

Source

pub async fn run_barrier(&mut self) -> LeanResult<BarrierReport>

Steps 2–7. barrier = one full publish cycle (the cadence arm).

Source

pub async fn cadence_barrier(&mut self) -> LeanResult<BarrierReport>

The floor tick’s barrier. Stamped cadence so the bucket can answer “which clock installed this boundary?” without the ack — the ack is a LOCAL file, and the fleet-visible question is asked by operators and by the gateway’s /status, neither of which can see it.

Source

pub async fn declared_barrier(&mut self) -> LeanResult<BarrierReport>

A barrier whose result somebody is going to ACK: a sentinel honor (D1) or the preStop drain (D10). Identical to run_barrier except that it confirms first-absence paths rather than acking a boundary that withholds them.

Source

pub async fn declared_barrier_as( &mut self, source: &str, ) -> LeanResult<BarrierReport>

declared_barrier under an explicit provenance stamp — the drain and the budget-deferred honor are the same barrier reporting a different clock.

Source§

impl Syncer

Source

pub async fn checkout(&mut self) -> LeanResult<CheckoutReport>

Materialize the workspace from the manifest. Idempotent across crashes (resume skips present paths); refuses over budget BEFORE the first byte.

Source

pub async fn checkout_scoped( &mut self, scope: Option<Vec<String>>, ) -> LeanResult<CheckoutReport>

Source§

impl Syncer

Source

pub async fn rescope( &mut self, target: Option<Vec<String>>, ) -> LeanResult<RescopeReport>

Move the workspace to target: stop holding what it no longer admits, and fetch what it newly does.

§Why this cannot be rm plus a scope edit

classify reads exactly the two states a narrow passes through: present-in-scan-absent-from-baseline is an upload, and present-in-baseline-absent-from-scan-and-prev_scan is a delete. So unlink-then-uncite crashes into publishing deletions, and uncite-then-unlink crashes into re-uploading identical bytes and re-citing everything just dropped. Neither order is safe alone. The invariant, stated for the model: a narrow is an unwatch, never an absence.

What makes it safe is the INTENT, not the order: the target is durable before the first mutation and cleared after the last, run_barrier replays any intent it finds before it does anything else, and the replay is idempotent. The order within still matters for the window where the intent itself is lost — uncite first, so the failure mode is a file that gets re-uploaded rather than one that gets DELETED from the bucket.

§Why the verb takes a SET

manifest::load fetches the pointer and every chunk regardless of scope, so the floor is one whole-manifest load per call. A per-path verb would pay that floor per path.

Source

pub async fn replay_scope_intent(&mut self) -> LeanResult<Option<RescopeReport>>

Finish a rescope a crash left half-applied. None when there is none in flight. Idempotent: running it twice is running it once.

Source§

impl Syncer

Source

pub fn control_path(&self, name: &str) -> PathBuf

Source

pub fn load_posture(&self) -> LeanResult<Option<SentinelPosture>>

Source

pub fn sentinel_preflight(&self) -> LeanResult<SentinelPosture>

The pre-existing-.flint/ pre-flight (D0.4).

Reserving .flint/ is a BREAKING change for a workspace already using it as data: its writes silently stop publishing and — worse — files literally named .flint/publish or .flint/sync would be consumed (renamed away) by the sentinel poll: a data grab from a non-participating workspace. When either signature is present, sentinel consumption is disabled for the workspace: the poll arm never arms, capabilities.json reports no verbs with the reason, and the operator surfaces it as SentinelVerbsActive=False.

The verdict is STICKY once written — see POSTURE.

Source

pub fn write_capabilities(&self, posture: &SentinelPosture) -> LeanResult<()>

Write .flint/capabilities.json for the current posture.

Source

pub fn read_capabilities(&self) -> Option<Capabilities>

Source

pub fn load_remote_seq(&self) -> RemoteSeq

Source

pub fn touch_remote_seq( &self, observed_seq: Option<u64>, observed_etag: Option<String>, integrated_seq: u64, ) -> LeanResult<()>

Move the ticker (D5). observed is what the barrier’s existing manifest HEAD (or its completed install) already told us — this never issues a request of its own.

updated_unix refreshes on every call (the liveness heartbeat); observed_seq/observed_etag change only when they change.

Source

pub fn carry_sync_request( &self, requested_unix: u64, requestor: &str, ) -> LeanResult<()>

Carry an advisory sync request into the ticker (D14). The asymmetry with boundary_request is about blast radius, not principle: a boundary publishes what is already on disk and touches no local file, whereas sync re-derives the tree against the current remote manifest and DELETES local files for remotely-deleted paths. Performing that on a remote’s say-so would upgrade what a leaked gateway bearer can do from “publish, plus hand over these N named objects” to “rewrite and delete across a running agent’s tree, at my timing, under a scope I choose”.

Source§

impl Syncer

Source

pub fn load_gauges(&self) -> LeanResult<Gauges>

Last written gauges, or defaults. Never fails on a corrupt file: a diagnosis surface that panics is worse than a stale one.

Source

pub fn note_boundary(&self, source: &str, seq: u64) -> LeanResult<()>

Record that a boundary installed.

Source

pub fn note_auth_pause(&self) -> LeanResult<()>

Record that the store refused our credentials on a renewal. First refusal wins: the gauge answers “since when”, so a second consecutive failure must not keep resetting the clock to now.

Source

pub fn clear_auth_pause(&self) -> LeanResult<()>

Clear the pause. Writes only on an actual transition: this runs on every successful renewal and floor tick, and the healthy path must not rewrite the file every tick for no change.

Source

pub fn write_gauges(&self, withheld: Option<Withheld>) -> LeanResult<Gauges>

Recompute and write the gauges. Deliberately synchronous and store-free: a scrape, a tick and an exec all cost zero bucket requests, and the type system is what says so.

Source§

impl Syncer

Source§

impl Syncer

Source

pub fn refuse_if_read(&self, what: &str) -> LeanResult<()>

The refusal every publishing or fencing path gives a reader. Refused because it is configuration, not contention: retrying cannot make a read-only syncer write.

Source

pub async fn reader_pull(&mut self) -> LeanResult<Pull>

One reader tick: two GETs, and a sync only when there is news.

Source§

impl Syncer

Source

pub fn load_pending(&self, verb: Verb) -> LeanResult<Option<PendingSentinel>>

Source

pub fn load_budget(&self) -> LeanResult<SentinelBudget>

Source

pub fn charge_budget(&self, published_bytes: u64) -> LeanResult<u64>

D3.1: charge the meter for a completed sentinel honor. published_bytes == 0 (a no-diff honor) costs NOTHING — the budget exists to bound work and a no-diff honor does none; the min-interval remains its only bound.

Source

pub fn sentinel_due(&self) -> LeanResult<Due>

Whether a standing pending sentinel may be honored on this tick.

Source

pub fn poll_sentinels(&mut self) -> LeanResult<Vec<Verb>>

One poll tick: lstat exactly two fixed paths — no inotify dependency, no directory scan, ~2 lstats/s when idle.

Returns the verbs whose sentinel was consumed (or coalesced) on this tick.

Source

pub fn recover_consume_staging(&mut self) -> LeanResult<()>

Recover a consume that crashed between the rename and the pending write. Called at startup, before the first poll.

Source

pub fn request_boundary( &mut self, nonce: &str, note: Option<String>, ) -> LeanResult<()>

A boundary asked for through a door that is NOT the file protocol — the gateway’s inbox field (§2.5) or the UDS socket.

It folds into the same pending record a .flint/publish touch would, which is the whole design of the layered doors: the extra doors are sugar over one consume path, so min-interval, coalescing, the work-metered budget, the covered-nonce ack and the crash rules apply to them without a second implementation that could disagree with the first.

Source

pub fn read_ack(&self, verb: Verb) -> Option<Ack>

Source

pub async fn honor_pending( &mut self, verb: Verb, forced: bool, ) -> LeanResult<Option<Ack>>

Honor a standing pending sentinel, if one stands and is due.

forced = this is a floor tick picking up a budget-deferred boundary; the ack is stamped sentinel-deferred. Ok(None) means nothing was owed (or it is not due yet).

Source

pub async fn honor_pending_as( &mut self, verb: Verb, forced: bool, source: Option<&str>, ) -> LeanResult<Option<Ack>>

honor_pending under an explicit provenance stamp. The drain needs it: it rewrites the ack to drain, and a manifest still stamped sentinel-deferred would have the bucket and the ack naming two different clocks for one boundary.

Source

pub async fn settle_pending_at_startup(&mut self) -> LeanResult<()>

The startup settle (the uniform crash rule). Runs before the first poll arms: a surviving pending must be honored, acked and retired before any fresh sentinel may be consumed.

Source§

impl Syncer

Source

pub async fn sentinel_tick(&mut self) -> LeanResult<Vec<Ack>>

One poll tick of the sentinel arm: consume what is there, then honor if the min-interval and the work budget allow.

In cadence mode the arm still consumes and honors — a sentinel triggers a fused barrier there too; the mode’s meaning is that there is no separate citation lane, not that the verbs are dead.

Source

pub async fn floor_tick(&mut self) -> LeanResult<FloorOutcome>

One floor tick: either honor a standing pending sentinel that the budget or min-interval held back — the boundary is honored by a REAL barrier, its ack stamped sentinel-deferred — or run the ordinary cadence barrier.

This is the arm B16 regresses on: the interval rewrite must not let the poll arm starve cadence.

Source

pub async fn drain(&mut self) -> LeanResult<Vec<Ack>>

The preStop drain (D10 rule 1): settle every owed ack BEFORE the lease release. A pending sentinel at SIGTERM is answered by the drain itself — the container-restart case, where the emptyDir and the agent both survive, must not strand a waiting agent.

Source§

impl Syncer

Source

pub async fn sync(&mut self) -> LeanResult<SyncReport>

Whole-tree sync — exactly as shipped, including advancing seq/manifest_etag.

Source

pub async fn sync_scoped( &mut self, scope: Option<Vec<String>>, ) -> LeanResult<SyncReport>

Source§

impl Syncer

Source

pub fn trace(&self, ev: &str, fields: Value)

Emit one trace event, when the trace is on.

Source

pub fn trace_requests(&self) -> Value

The store’s request counts, as a trace field.

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