Skip to content

chore: release v0.6.41 — ship pipeline auto-commit - #624

Merged
githubrobbi merged 11 commits into
mainfrom
release/v0.6.41
Sep 19, 2026
Merged

githubrobbi merged 11 commits into
mainfrom
release/v0.6.41

Conversation

@githubrobbi

Copy link
Copy Markdown
Collaborator

Summary

just ship Phase 2 auto-commit for v0.6.41 — the [workspace.package].version bump in Cargo.toml. This PR routes that commit through branch-protection rules. Once it merges to main, run just release-tag to cut the signed v0.6.41 tag, which fires release.yml and builds the cross-platform binaries + GitHub Release v0.6.41. (No auto-tag on merge — the tag step is manual on-demand, Path B.)

Auto-merge

--auto --squash is queued — GitHub will merge as soon as the required status checks pass. Squash is required because main-protection mandates signed commits, and GitHub's rebase-auto-merge cannot sign the rebased commit; the squash-merge commit is signed by GitHub's own key, which satisfies required_signatures: true. The original author's signed commit remains verifiable in the PR branch history.

After merge

The auto-commit lived only on release/v0.6.41, so local main never drifted — sync it with a plain git pull --ff-only origin main (no reset --hard needed).

…rlapped path

A production MFT read hung for four hours on a fragmented 14 GB table
(73 extents) and was only ended by an external supervisor at its hard
ceiling. Zero bytes read, zero CPU, no error, nothing logged.

The hang sat immediately after "MFT extents bootstrapped from FRS 0
$DATA runlist (broker path)", in `get_mft_bitmap_internal`, which is the
very next step and is silent unless the verbose flag is set.

That function seeked and read on `self.handle` with `SetFilePointerEx`
plus a NULL-`lpOverlapped` `ReadFile`. On the broker path `self.handle`
is a `DuplicateHandle` copy of the Access Broker's `FILE_FLAG_OVERLAPPED`
volume handle, which has no synchronous file pointer: the seek is
meaningless and the read is issued asynchronously with nowhere to report
completion, so the call never returns and never fails. The only error
handling present was a graceful fallback on `Err`, and a hang is not an
`Err`.

`read_boot_sector` carried the identical pattern and is fixed with it.

Both now use `read_handle_at`, the overlapped-offset primitive that
already carries a 30 s completion deadline and a bounded abort retry,
and which is correct on a synchronous handle too (the offset in the
OVERLAPPED is honoured directly). This is the third site in this crate
to need that remedy after the `$UpCase` read, so the reasoning is
recorded at both call sites rather than in a commit message alone.

Sparse extents are now skipped rather than read from a negative LCN,
and a negative byte offset falls back to the all-valid bitmap instead of
being cast into a nonsense position.
…orever

Four IOCP readers waited on `GetQueuedCompletionStatus` with `u32::MAX`
(INFINITE) and, when the call failed, logged a warning and `continue`d
without clearing or re-issuing the failed slot. The in-flight count then
drained toward zero while `completed_count < total_io_ops` stayed true,
so the next wait parked in the kernel on a completion port with no
pending I/O. No CPU, no bytes, no error, no end.

That is the same signature as the four-hour production hang fixed in the
previous commit, and the 30 s completion deadline that exists for exactly
this reason had only ever been applied to one of the five readers.

All four now poll at `IOCP_WAIT_POLL_INTERVAL_MS` and fail with
`wait_deadline_exceeded` once `IOCP_WAIT_COMPLETION_DEADLINE` passes with
no completion at all, matching `to_index.rs`. A genuine wait failure now
returns `classify_wait_error_code` rather than being swallowed.

`iocp_wait_for_completion` additionally changes shape from
`Option<..>` to `Result<..>`. Returning `None` made the caller report a
successful no-op completion, which both stalled the loop and, had it ever
unwedged, would have produced a silently truncated index. A completion
that matches no in-flight slot is likewise an error now, not a shrug.
One production `uffsd.log` reached 305 MB, dominated by an INFO line
reading "MFT is fragmented" emitted roughly seventy times a minute.

`MftExtentMap::new` logged the fragmentation summary, and the USN
journal poll rebuilds an extent map on every tick that carries a created
or renamed record (`usn::read_targeted_frs_records`, 500 ms cadence per
drive). The result was self-feeding: writing the log line created a USN
record, which triggered a poll, which rebuilt the map and wrote the line
again.

Constructors should not log. `new` is now silent and `const`, and the
layout summary moves to an explicit `log_layout()` that the ten one-shot
cold-load, capture and benchmark call sites invoke. The two hot paths —
the journal poll and `lcn_resolve::for_each_record`, both driven by the
same 500 ms tick — deliberately do not.

No diagnostic is lost: a cold drive load still reports its fragmentation
exactly once, which is the moment the information is actually wanted.
An MCP `uffs_search` sat silent for thirty minutes. The daemon never
logged the request, neither side reported a timeout, and an identical
call a moment later succeeded.

`send_request` wrapped each individual `read_line` in a five-minute
timeout, re-armed on every iteration of the notification-routing loop.
The daemon broadcasts a `StatsHeartbeat` notification to every connected
client about every thirty seconds, and each one reset the clock. A
request the daemon never answered therefore waited forever while the
code read as thoroughly timed-out.

The deadline is now absolute: computed once before the first byte goes
out and enforced with `timeout_at`, so routing a notification cannot buy
the response more time. The writes are bounded by the same deadline —
they had none at all, and on Windows a full 64 KB pipe buffer blocks the
caller indefinitely when the daemon stops draining.

Budget stays 300 s and is overridable through the same
`UFFS_CLIENT_TIMEOUT_SECS` variable the synchronous client honours,
including `0` to disable it under a debugger. It is deliberately more
generous than the sync client's 60 s: this is the path that waits while
a cold drive warms.

Two regression tests drive an in-memory duplex on a paused clock, so 300
virtual seconds cost no wall time: one asserts that a heartbeating
daemon which never answers still trips the deadline, the other that a
response arriving after notifications is returned normally.
On 2026-09-04 an unrelated automation agent ran `Stop-Process -Name
uffs-broker -Force` while clearing a stuck scheduled task. The broker
exited with 0xFFFFFFFF, the SCM logged event 7034 and took no action,
and the box ran without a broker for about seven hours until a human
restarted it by hand. Every non-elevated MFT read failed for that whole
window.

Three separate gaps made a one-second problem last seven hours.

The service had no recovery actions, so the SCM's response to a killed
broker was to write an event-log line and stop. `--install` now
configures a restart ladder (5 s, 10 s, 60 s, then give up) with a
24-hour failure-count reset, plus the failure flag — without the flag,
recovery applies only to an unexpected termination and a reported
non-zero exit is treated as a deliberate stop. A failure to set them is
reported but not fatal: an installed, running broker with no ladder
beats no broker.

A serve loop that returned `Err` was logged and then reported to the SCM
as a clean stop, so even a configured ladder would not have fired. It
now reports `ERROR_SERVICE_SPECIFIC_ERROR`, which is what actually gets
the broker restarted.

The service kept no log at all: `init_tracing` writes to stdout and a
service has none, so the broker's own account of the incident was
written nowhere and the timeline had to be rebuilt from the Windows
Security log. The SCM path now logs to `<log-dir>/uffs-broker.log` with
daily rotation and seven files of retention.

`--repair` applies the ladder to an already-installed service in place.
Reinstalling would mean a stop/delete/create cycle, and a window with no
broker at all, purely to change two SCM settings.

The usage banner and the `--self-test-vss` argument lookup move to
`broker/cli.rs` to keep `broker.rs` under the 800-LOC ceiling.
Both `uffsd` and `uffsmcp` opened their log with
`tracing_appender::rolling::never`, which appends forever with no size
cap, no rotation and no retention. One production `uffsd.log` reached
305 MB.

The dominant source of that volume is fixed at its root in this same
series, but an unbounded log is a standing hazard independent of what is
writing to it: these are processes that run for weeks and nothing ever
truncates them. Rotation is not automatic in `tracing` — it is an
opt-in, and so is pruning: `Rotation::DAILY` alone still keeps every
file ever written, so `max_log_files` is a second, separate opt-in.

Both now rotate daily and keep seven files. The caller's directory and
file stem are preserved, so `--log-file /var/log/uffsd.log` becomes
`/var/log/uffsd.<date>.log`. A builder failure degrades to stdout
logging rather than killing a detached daemon at startup, matching the
existing treatment of an uncreatable log directory.
bitflags 2.13.1 -> 2.13.2, clap 4.6.6 -> 4.6.7, crossbeam-channel
0.5.16 -> 0.5.17, rmcp 3.1.4 -> 3.4.0, smallvec 1.15.2 -> 1.16.1, toml
1.1.4 -> 1.1.6, uuid 1.26.0 -> 1.26.1.

crossbeam-channel is the one worth calling out: 0.5.17 fixes undefined
behaviour in the bounded channel when a `SelectedOperation` is leaked,
plus an overflow in bounded-channel initialization. UFFS uses bounded
crossbeam channels in the MFT reader pipeline, so this is wanted on its
merits rather than for currency.

rmcp 3.4.0 deprecates the `ServerInfo` alias in favour of
`ServerConfig`; the handler is migrated rather than having the warning
suppressed.

zerocopy stays at 0.8.56. 0.8.57 shipped on 2026-09-08 but does not
carry the fix for the `clippy::empty_enums` false positive on its
generated derive code — verified by inspecting `const_block()` in the
published `zerocopy-derive-0.8.57` source, whose emitted allow-list
still lacks the lint. Upstream google/zerocopy#3414 and its proposed fix
are both still open. Detail is recorded on issue #617, including the new
finding that the lint has moved from `clippy::pedantic` to
`clippy::nursery`, which the workspace denies just the same.

Supply chain: `cargo vet regenerate imports` picked up upstream audits
covering clap, clap_builder, clap_derive and toml, and retired the
`shlex` and `simdutf8` exemptions. The remaining six deltas are audited
here from the real diffs rather than waved through. Notably the three
lines a grep flags as new `unsafe` in smallvec are the pre-existing
`MaybeUninit::uninit().assume_init()` block re-wrapped by rustfmt, with
its SAFETY comment intact, and the only `std::fs`/`process`/`env`
additions in the 6731-line rmcp diff are five lines in a Unix-socket
test fixture. Exemptions fall from 273 to 167.
`uffs-gen-workflow --check` validated `pr-fast.yml` structurally and
nothing else. The release, preview, nightly, tier-2 and dependabot
workflows are hand-written and were outside every check — which is
exactly where they drift, because they share four things by copy: action
pins, toolchain versions, per-target RUSTFLAGS, and nextest profile
names.

The gate gains four properties over every `*.yml` under
`.github/workflows`, read-only like the rest of the tool:

5. one SHA per action across all workflows;
6. `ziglang` / `cargo-zigbuild` versions matched against a new
   `[toolchain]` table in `gates.toml`;
7. matrix rows and job-level `RUSTFLAGS` matched against `[[target]]`
   rows, with `target-cpu=native` refused outside comments;
8. every `--profile` on a `nextest run|archive` command resolved against
   `.config/nextest.toml`.

The two manifest tables sit before the first `[[gate]]`, where
`check_gates_drift.sh`'s awk resets on any table header and so ignores
them. No new gate, no hook regeneration, no `pr-fast.yml` change: the
existing `workflow-drift` gate simply sees more.

First run over the real tree is clean, and the two known false-positive
shapes stay quiet: comments that forbid `target-cpu=native`, and
`rustup toolchain install --profile minimal`. So does this repo's
`cargo build --profile ship`, since property 8 is scoped to nextest
commands.

Ported from the docenta side of the CI-posture alignment (docenta commit
63cf6f6), which added the same four properties to its copy of this
generator. Four unit tests come with it.
codeql-action init and analyze 4.37.8 -> 4.37.9, release-plz/action
0.5.131 -> 0.5.132, softprops/action-gh-release to its current v3 SHA.

Supersedes dependabot PR #622. The new cross-workflow consistency gate
added earlier in this series confirms all five pins land on one SHA per
action.
`cargo deny` failed the ship on rustls 0.23.43: it accepted TLS 1.3
handshake messages sent at the wrong encryption level when they followed
a key-changing message in the same record, so a plaintext
`EncryptedExtensions` packed into the same record as `ServerHello` was
accepted. RFC 8446 section 5.1 requires the connection be terminated
with an "unexpected_message" alert instead.

The transcript stays authenticated, so this is not a handshake-forgery
path; the practical effect is that a peer can send messages in plaintext
that should have been encrypted without rustls objecting. Same bug as Go
CVE-2025-61730.

rustls reaches UFFS transitively through polars -> object_store ->
reqwest -> hyper-rustls, so no direct dependency changes.

The fix is in the deframer: `is_aligned` now means "no pending handshake
message, complete or partial" rather than "no partial fragment". Audited
in the same commit, along with the accompanying `PeerMisbehaved`
tightening and the `NonEmpty` type parameter on `PayloadU24`.
@githubrobbi
githubrobbi added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit 5892fc8 Sep 19, 2026
23 checks passed
@githubrobbi
githubrobbi deleted the release/v0.6.41 branch September 19, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant