refactor(logging): one verification-failure rule for both receive paths - #7247
Draft
oferchen wants to merge 2 commits into
Draft
refactor(logging): one verification-failure rule for both receive paths#7247oferchen wants to merge 2 commits into
oferchen wants to merge 2 commits into
Conversation
upstream receiver.c:1071-1091 is a single rule with five decisions in it - the severity selected on `redoing`, the emission gate, the `keptstr` choice, the retry suffix and the format string. Each decision reads state oc keeps in a different place on each of its two receive paths, so writing the rule beside each path is how the two wordings drift apart. Hoist it into crates/logging as `verification_failure`, taking one struct whose seven fields are each a single upstream variable: redoing, read_batch, stdout_format_has_i, keep_partial, has_partial_path (`partialptr != NULL`), partial_dir and inplace. `INFO_GTE(NAME, 1)` is read inside through `logging::info_gte`, so that disjunct of the gate is single-sourced too. logging is the right home and LogCode the right return type: the crate has no workspace dependencies, so it cannot take transfer's PartialMode or protocol's MessageCode, and it already owns LogCode - upstream's `enum logcode`, holding the FERROR_XFER/FWARNING this rule selects between. Callers route the code to their own sink. The network receiver keeps only `partial_state`, which projects its session-wide PartialMode onto upstream's three separate variables; `verification_kept_str` and `reports_verify_warning` are deleted. The projection is equivalent to the deleted helper on every input, so the wording on that path is unchanged. Tests are table-driven over the pure function. Seven keptstr rows run through both severities, two of them unreachable from the command line but present to pin the if/else-if ORDER at receiver.c:1074-1079 - a rewrite as three independent predicates passes every reachable row and fails exactly those two. Four gate rows cover all three disjuncts of receiver.c:1072, including that the FERROR_XFER form is unconditional and carries no retry suffix even under --read-batch. The local-copy emit site is not merged yet; it delegates in a follow-up.
oferchen
force-pushed
the
fix/hoist-verify-failure
branch
from
August 7, 2026 00:13
60555f8 to
647b79b
Compare
oferchen
force-pushed
the
fix/hoist-verify-failure
branch
from
August 7, 2026 00:45
b86d128 to
647b79b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The rule
receiver.c:1071-1091is one rule with five decisions in it:Severity, emission gate,
keptstr, retry suffix and format string. Every one ofthem reads state oc keeps in a different place on each of its two receive
paths, which is exactly the shape that drifts when the rule is written twice.
Change
crates/logging/src/verify_failure.rsholds the rule once:VerifyFailurehas seven fields and each one is a single upstream variable,named for it:
redoing,read_batch,stdout_format_has_i,keep_partial,has_partial_path(partialptr != NULL),partial_dir,inplace.INFO_GTE(NAME, 1)is read inside the function throughlogging::info_gte, sothat disjunct of the gate is single-sourced too.
crates/loggingis the right home andLogCodethe right return type: thecrate has no workspace dependencies, so it cannot take transfer's
PartialModeor protocol's
MessageCode, and it already ownsLogCode- upstream'senum logcode, with theErrorXfer/Warningthis rule selects between - andthe
INFO_GTElookup. Callers route the code: the network receiver converts itwith the existing
MessageCode::from_log_code; the local executor writes it towhichever stream
log.c:313-316routes that code to.The network receiver keeps only what is genuinely its own:
partial_state,which projects its session-wide
PartialModeonto upstream's three separatevariables.
verification_kept_strandreports_verify_warningare deleted.No behaviour change on the transfer path. The projection is equivalent to the
deleted
verification_kept_stron every input:PartialModeinplaceNoneNonePartialPartialDirTests
Table-driven, in
logging, over the pure function.Seven
keptstrrows, each run through both severities:The two bold rows are unreachable from the command line - upstream sets
keep_partialwhenever--partial-diris given, and rejects--inplace --partial-diroutright - but they are what pin the if/else-ifORDER. Rewriting the chain as three independent predicates passes every
reachable row and fails exactly these two.
Four gate rows cover all three disjuncts of
receiver.c:1072: silent bydefault, reported under
INFO_GTE(NAME, 1), reported understdout_format_has_i, and reported unconditionally when the severity isFERROR_XFER. Plus: the ERROR form carries no retry suffix even underread_batch- which catches a hoist that appendsredostrbefore checkingredoing.On the transfer side the old
keptstrunit test is replaced by two that coverwhat remains there: the
PartialModeprojection, and an end-to-end check thatevery
keptstris still reachable through this receiver's own types.Second call site
The local-copy emit site is
warn_verification_failedin #7235, which is notmerged yet. It hardcodes
keptstr = "retained",redostr = " (will try again)"and the severity, and derives the first from local append state rather than from
inplace- a third copy of the rule in prose form. It also spellsstdout_format_has_iasis_itemize_active()where the receiver spells itout_format_forwards_i. Delegating it is a follow-up on that PR; this onelands the shared rule and the network call site.
Verification
Run on the aarch64 Linux host, since GitHub Actions is not scheduling. Exit
codes checked directly, never through a pipe.
cargo fmt --all -- --check- exit 0cargo clippy --locked --workspace --all-targets --all-features --no-deps -- -D warnings- exit 0cargo nextest run --workspace --all-features --no-fail-fastA
clippy::nonminimal_boolfinding on thekeptstrchain was fixed by namingthe positive condition (
survives = inplace || (keep_partial && has_partial_path))and negating once - not by a waiver.
Does this RETIRE the drift risk, or only REDUCE it?
Retired, as of this PR, conditionally. Stated plainly rather than assumed:
On current master plus this branch there is exactly one implementation and one
call site.
verification_kept_strandreports_verify_warningare deleted;the network receiver computes no part of the rule locally. What remains in
transferispartial_state, which is a projection of oc's ownPartialModeonto upstream's three variables - not a second copy of the decision. A
single-site test therefore suffices, and no cross-implementation test is
required or possible today, because there is nothing to compare against.
The condition: #7235 has not merged. It adds the local-copy emit site, and
as written it hardcodes
keptstr,redostrand the severity, deriving thefirst from local append state rather than from
inplace. If it lands that way,the rule is back in two places and the risk is only reduced - at which point a
cross-implementation test feeding both sites the same input becomes mandatory,
and I will own it. The exact delegating call site has been handed to that PR's
author to fold into the rebase in flight, so the intended end state is one
implementation with two call sites.
Verified per the standing question:
keptstr'sinplaceinput on the transferpath is the post-#7243 implied flag. The receiver passes
pending.is_inplace,which is
resolve_use_inplace(config.write.inplace, ..), andServerConfig::apply_append_implies_inplacesets that flag for--append. So anappend whose verification fails reports its update "retained", not "discarded",
and it reaches that string through the same input upstream uses.
Boundary with #377
LogCodeit carries.LogCoderoutes, mirroringrwrite(log.c:251).verification_failurereturnsOption<(LogCode, String)>and does no routing,so the funnel absorbs it without rework: callers keep whatever sink they use
today and #377 collapses the sinks later. That is why the return type is
LogCodeand not an already-chosen stream.The gate input must stay format-derived
options.c:2345-2358feeds one variable from two sources, and-irewritesstdout_formatto"%i %n%L":So a format-derived value catches both
-iand a bare--out-format='%i%n'given without-i; an-iboolean misses the second.The transfer receiver's
out_format_forwards_iis the faithful spelling and iswhat feeds this field. The engine's
is_itemize_active()(a bool from-i) isnarrower - a real divergence, not a stylistic one. The field is documented
accordingly so nobody "simplifies" it back to the
-ibool.Also documented on the field: upstream's
stdout_format_has_iis a tri-state(0/1/2 -
2whenam_serverand the format carries%I, anditemize_changesis a counter at
options.c:1581, so-iialso yields 2). Several upstream sitestest
> 1(generator.c:583,1010,1138,hlink.c:400,log.c:832). This gate isnot one of them -
receiver.c:1072tests plain truthiness - soboolis exacthere, and the doc says so explicitly to stop a future caller reusing the field at
a
> 1site and silently losing the-i/-iidistinction.Checked and clean: oc does not conflate
stdout_format_has_iwithlogfile_format_has_i(receiver.c:644's server-side variable);generator/protocol_io.rs:606already distinguishes them.