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 held lease, once claimed. Barriers refuse to run without it.

§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 verify_not_deposed_pub(&self) -> LeanResult<()>

The in-loop honor paths’ fence check (boundary-verbs plan D2): Syncer::sync carries no lease/epoch check of its own, so a straggler consuming a sync sentinel between deposal and its next cooperative fence would apply the successor’s manifest onto its zombie tree and ack SUCCESS.

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 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, fenced: bool, ) -> 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_stage(&self) -> LeanResult<PendingStage>

Source

pub fn save_stage(&self, s: &PendingStage) -> LeanResult<()>

Source

pub fn orphans_key(&self) -> String

The key D9’s durable orphan summary lives at.

Source

pub async fn surface_orphans(&self, stage: &PendingStage) -> LeanResult<bool>

Surface the staged-but-uncited set into the BUCKET (D9).

pending.json lives on the emptyDir, which is exactly the thing a pure-spot replacement destroys — so on the routine failure of this fleet the pending record can name nothing, and the operator (a different process, often a different cluster) has no way to know that durable work is sitting uncited. This is that summary, and it is written where it survives cluster loss.

Written only when the candidate set CHANGES: a re-PUT per tick would be a request the design does not need, and an empty doc rewritten forever would be worse than none. The clearing write matters as much as the surfacing one — a stale summary claiming candidates pages an operator about work that was cited minutes ago.

Source

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

The upload lane: durability, every floor tick, no citation.

Source

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

The lane pass behind a DECLARED boundary — a publish sentinel (D1 × D6) or the preStop drain (D10). The citation that follows is going to be acked, so a delete the agent made before the touch may not sit out the boundary waiting for a second walk: confirm_absences takes that observation by lstat instead.

Source

pub fn repair_candidates(&self) -> LeanResult<Vec<String>>

Paths whose INTEGRATED object (the baseline) differs from the manifest’s citation: a consumed HITL write, or checkout’s S3-wins adoption. The fused barrier has carried this repair since the amputation leg; gated mode never runs the fused barrier, so without the same pass here an acked HITL write is adopted into the tree, scans clean forever after, and stays cited at its PREVIOUS version — invisible to exactly the pinned readers D13 governs.

Source

pub fn citation_due( &self, sentinel_pending: bool, ) -> LeanResult<Option<CitationSource>>

Which coherent point, if any, is due right now.

Source

pub async fn citation_pass( &mut self, source: CitationSource, ) -> LeanResult<CitationReport>

Source

pub async fn gated_tick( &mut self, sentinel_pending: bool, ) -> LeanResult<(LaneReport, CitationReport)>

The gated floor tick: lane, then a citation if one is due.

Both halves are reported because the floor arm has to describe them separately — a tick that staged 40 files and cited nothing is the mode working, and a tick that staged nothing and cited nothing is idle. Collapsing them into one “no change” would make gated mode indistinguishable from a wedged one in the log.

Source

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

The startup gate for gated mode (D8, D11): probe the version surface BEFORE a single byte is staged, and refuse rather than degrade. Inert in cadence/hybrid, which need no version surface at all — a gate that took every default workspace down with it would be worse than the hazard.

Source

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

flint-sync recover-staged (D9): re-cite durable-but-uncited work as ONE flagged boundary — a manifest CAS with no data movement, because the versions already exist.

The routine path into this verb is a pure-spot pod replacement: the emptyDir that held pending.json is gone, so the pending record can name nothing and the bucket is the only source of truth. Two shapes are recovered, and they overlap:

  • uncited work — the key’s current version is not the one the manifest cites (or the key is not cited at all: a brand-new file staged and never installed);
  • a dangling citation — the cited version no longer exists, because gated staging made it NONCURRENT and the retention backstop ran its clock against live cited data (D8’s inversion). Checkout refuses on this rather than serving a hole, so recovery is what makes the workspace usable again.

Recovery rolls forward, onto the newer bytes, and says so: every re-citation writes a conflict record. It is deliberately NOT a three-way merge — a replacement pod has no merge base, and merging against an empty one would classify the entire existing manifest as foreign and queue the whole tree into the HITL inbox.

Source

pub fn is_gated(&self) -> bool

Is this workspace gated?

Source

pub async fn versioning_conformance(&self) -> LeanResult<()>

The versioning conformance probe (D8) — the SHARED implementation in flint_store::probe, because the operator runs the same probe on its reconcile cadence and the two verdicts must never disagree. A workspace the operator calls conformant and the syncer refuses to start is worse than either answer alone.

A project-scoped proxy that strips x-amz-version-id degrades gated mode SILENTLY — pending entries would carry None, and citation would fall back to etag semantics on a key whose current version is uncited, which is precisely the torn view the mode exists to prevent. So the probe REFUSES rather than degrading, and is re-run at startup and on the operator cadence: proxies upgrade, and a bucket’s posture changes under you.

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 citation installed. Bumps the forced counter when the source was a cap rather than a declared coherent point.

Source

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

Record that bytes were made durable without being cited (the gated upload lane).

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 the healthy path must not rewrite the file every heartbeat for no change.

Source

pub fn write_gauges( &self, fenced: bool, 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 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 refuse_what_this_incarnation_can_never_honor( &mut self, ) -> LeanResult<bool>

D2’s refused-ack rule for the restarted claimant, which was the last open corner of the protocol.

A syncer SIGKILLed mid-honor runs no cooperative fence path, so nothing settles the pending sentinel it left in the emptyDir. If the kubelet then restarts it over that surviving tree while a successor holds the lease, it blocks in claim — the successor’s token keeps advancing, so quiet polls never accumulate — and settle_pending_at_ startup is unreachable, because that runs only after claim returns. The agent is left polling an ack that will never come, behind a capabilities.json that still says state: "live" with live verbs.

This process can never honor what it owes. It says so, once, on the first Waiting poll: a refused-fenced ack per owed verb and a fenced marker, so the agent stops waiting and the marker stops lying.

Gated on a pending record actually standing. A fresh replacement pod waiting out its 60 s of quiet polls is HEALTHY, not fenced, and its emptyDir carries no pending record — so it takes none of this.

Source

pub fn refuse_pending( &mut self, verb: Verb, observed_epoch: Option<u64>, ) -> LeanResult<()>

The refused ack (D2). Deposal must never strand a waiting agent.

Source

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

Flip the capability marker to fenced: no verbs, state: "fenced", so agents stop touching sentinels on a zombie.

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 heartbeat_tick(&mut self) -> LeanResult<()>

The D12 heartbeat renewal arm, as a tick of its own.

It is a method rather than three lines in the run loop because of what it owes on a fence. This arm renews on its own non-resettable interval — min(floor, 30) s, decoupled from publish cadence, which is the whole reason D12 added it — so on deposal it is usually the FIRST arm to find out, ahead of the floor tick and ahead of any poll that has nothing to honor. Returning from here without settling would strand every owed ack and leave capabilities.json advertising live verbs on a zombie: the hole D2’s refused acks exist to close, re-opened at the arm D12 added to close a different one.

Source

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

One floor tick: renew (D12), then 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>

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.