Skip to main content

WriteReport

Struct WriteReport 

Source
pub struct WriteReport {
    pub expected_paths: HashSet<PathBuf>,
    pub changed_paths: HashSet<PathBuf>,
    pub refused_paths: BTreeSet<PathBuf>,
    pub refused_drifted_paths: BTreeSet<PathBuf>,
    pub user_owned_paths: BTreeSet<PathBuf>,
    pub refused_create_once_paths: BTreeSet<PathBuf>,
}

Fields§

§expected_paths: HashSet<PathBuf>§changed_paths: HashSet<PathBuf>§refused_paths: BTreeSet<PathBuf>

Paths the ownership guard declined to write.

Recorded rather than only logged, because a refusal is otherwise invisible to every downstream signal: the guard continues before the path reaches expected_paths, so orphan sweeps, freshness checks and the changed count all behave as though alef never intended to write the file. A permanently frozen file is then indistinguishable from one alef simply does not manage.

Unlike an ordinary skip the condition never clears on its own, and the remedy — alef adopt — is a human action. A human cannot act on a number nobody reports, so this has to be visible rather than inferred from what did not change.

A BTreeSet rather than a Vec: the same path can be refused by more than one guard site in a run, and the report is read by a person, so it must not repeat itself or reorder between runs. ~keep

§refused_drifted_paths: BTreeSet<PathBuf>

The subset of Self::refused_paths whose withheld content actually DIFFERS from what is on disk, once the provenance header alef itself would add is discounted ([matches_alef_output]).

THE DEFECT this closes: the refusal tally reported how many files were withheld and nothing about what was withheld, so a refusal that would have changed a real byte and a refusal that would only have stamped a header on already-correct content produced the same number and the same sentence. Both are common – a body-identical, header-missing file is refused by the guard exactly as a stale one is (the writers’ unchanged check compares the header-stamped bytes) – and only one of them is a live defect. The measured consequence: a generated test-app installer bakes the release version into its own bytes, the guard refused the rewrite on every run, and three separate consumer repositories shipped an installer pinned to a stale release for weeks, because from outside a refusal on version-derived content reads exactly like a file that is already up to date.

Populated at the guard, not recomputed later: both writers hold the disk bytes and the prepared output in the same scope at the moment they decide, so this costs one string comparison and no extra I/O. A caller that inserted into Self::refused_paths directly would silently reintroduce the undifferentiated tally, which is why both guards go through Self::refuse_text/Self::refuse_drifted instead. ~keep

§user_owned_paths: BTreeSet<PathBuf>

Paths this run left alone because [workspace.ownership] user_owned (crate::core::config::OwnershipConfig) declares them owned by the consuming repository rather than by alef.

A separate set from Self::refused_paths because it is a separate FACT, not a softer wording of the same one. A refusal is alef reporting that it wanted to write a file and could not prove it may – an unresolved condition with a human remedy. A declared skip is alef reporting that it was told not to, by a committed line of config someone can read. Folding the two would leave the operator with a failure tally that only goes down by deleting the declaration, which is exactly the stable bad state the declaration exists to end.

Reported rather than silent for the same reason refused_paths is: a run that wrote nothing because 17 paths were declared must say so. See report_user_owned_skips. ~keep

§refused_create_once_paths: BTreeSet<PathBuf>

The subset of Self::refused_paths that [crate::cli::commands::adopt::is_create_once_seed] classifies as a create-once seed – the identical predicate alef adopt gates --clobber-create-once-seeds on, asked by the guard at the moment it refuses rather than re-derived here.

THE DEFECT this closes: before this field existed, every refused write was reported under one heading – “content DIFFERS … stale until adopted or deleted” – and pointed at alef adopt <path> regardless of whether that path was a create-once seed. For a seed, alef adopt --write then refused the exact remedy this warning printed, naming a flag (--clobber-create-once-seeds) the warning never mentioned. Measured in a consumer repo: 13 of 17 refused writes were create-once seeds (*.csproj, pubspec.yaml, mix.exs, go.mod, pom.xml, build.gradle.kts, gradle-wrapper.properties, package.json, Gemfile, Package.swift, build.zig, build.zig.zon), and the two subsystems also disagreed about which direction was dangerous: this warning implied inaction was the risk (“stale until adopted”), while alef adopt correctly treats ADOPTION as the risk – the on-disk copy has almost certainly grown past alef’s placeholder, and adopting consents to alef replacing it on the next overwriting regen. report_refused_writes now reports this subset separately, in adopt’s own words, instead of asserting the opposite of what alef adopt says about the same path. ~keep

Implementations§

Source§

impl WriteReport

Source

pub fn changed_count(&self) -> usize

Source

pub fn expected_count(&self) -> usize

Source

pub fn refused_count(&self) -> usize

Source

pub fn refused_drifted_count(&self) -> usize

How many refused writes had different content to deliver – see Self::refused_drifted_paths.

Source

pub fn user_owned_count(&self) -> usize

How many paths this report left alone on the strength of [workspace.ownership] user_owned.

Source

pub fn refused_create_once_count(&self) -> usize

How many refused writes are create-once seeds – see Self::refused_create_once_paths.

Source

pub fn refuse_drifted(&mut self, path: &Path, create_once: bool)

Record a refused write whose withheld content is known to differ from the bytes on disk.

For the branches where no comparison is needed to know the answer: a binary target the guard reached only after an exact byte comparison already failed, and a text target whose existing bytes are not valid UTF-8 at all (alef’s prepared output always is, so they cannot be equal, and the file cannot be compared to say anything narrower).

create_once is the caller’s own answer from [crate::cli::commands::adopt::is_create_once_seed] on the GeneratedFile being refused – passed in rather than re-derived here, because this module has no access to the original file once the caller is down to a path and prepared bytes. ~keep

Source

pub fn refuse_text( &mut self, path: &Path, existing: Option<&str>, generated: &str, create_once: bool, )

Record a refused text write, classifying it against the bytes already on disk.

existing is None when the file could not be read as text, which classifies as drifted for the reason Self::refuse_drifted’s doc gives. create_once is threaded through to it for the same reason. ~keep

Source

pub fn absorb_unwritten(&mut self, other: &WriteReport)

Fold another phase’s refusals into this report.

A run writes through several independent phases — bindings, service API, type stubs, public API, scaffolding — each returning its own report. The refusal summary is a run-level fact addressed to an operator, so reporting per phase understates it: the reader works the list they were shown and is left with the refusals from every other phase, unlisted and with no remaining signal that they exist. Only refused_paths merges; the changed and expected sets stay per-phase because their counts are reported per phase and summing them would double-count a path two phases both intended.

Folds BOTH not-written sets – refusals and declared user-owned skips – through this one call rather than adding a second method beside it, so the ~13 absorb_unwritten call sites cannot end up folding one set and dropping the other. That is not hypothetical here: alef all already shipped a bug where a count-only wrapper discarded refused_paths for a whole class of writes, and the run reported success while the guard had silently refused thousands. The drifted subset folds here too, for the same reason and in the same call: a per-phase split of the refusal tally would let one phase’s stale withheld content vanish from the run-level report. ~keep

Trait Implementations§

Source§

impl Debug for WriteReport

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for WriteReport

Source§

fn default() -> WriteReport

Returns the “default value” for a type. 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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

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.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more