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
impl WriteReport
pub fn changed_count(&self) -> usize
pub fn expected_count(&self) -> usize
pub fn refused_count(&self) -> usize
Sourcepub fn refused_drifted_count(&self) -> usize
pub fn refused_drifted_count(&self) -> usize
How many refused writes had different content to deliver – see
Self::refused_drifted_paths.
Sourcepub fn user_owned_count(&self) -> usize
pub fn user_owned_count(&self) -> usize
How many paths this report left alone on the strength of [workspace.ownership] user_owned.
Sourcepub fn refused_create_once_count(&self) -> usize
pub fn refused_create_once_count(&self) -> usize
How many refused writes are create-once seeds – see Self::refused_create_once_paths.
Sourcepub fn refuse_drifted(&mut self, path: &Path, create_once: bool)
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
Sourcepub fn refuse_text(
&mut self,
path: &Path,
existing: Option<&str>,
generated: &str,
create_once: bool,
)
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
Sourcepub fn absorb_unwritten(&mut self, other: &WriteReport)
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
impl Debug for WriteReport
Source§impl Default for WriteReport
impl Default for WriteReport
Source§fn default() -> WriteReport
fn default() -> WriteReport
Auto Trait Implementations§
impl Freeze for WriteReport
impl RefUnwindSafe for WriteReport
impl Send for WriteReport
impl Sync for WriteReport
impl Unpin for WriteReport
impl UnsafeUnpin for WriteReport
impl UnwindSafe for WriteReport
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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