chore(deps): rmcp 2.1 + aes-gcm 0.11 with full vet coverage, SEP-2577 confinement, exemption burn-down#504
Merged
Merged
Conversation
…tion On Windows, std::fs::canonicalize returns the \\?\ extended-length form, which never matches a bare C:\... PATH entry. The live v0.6.18 run showed every binary (including the active uffs) mislabeled 'off-path' and the install dir displayed as \\?\C:\Users\rnio\bin, and it means the PATH-safety gate can't recognize a PATH entry either. Add strip_verbatim_prefix and apply it at the canonicalize sites (detect's upsert_root, the uninstall PATH scan) and to the current-exe dir in search_dirs, so stored dirs match plain PATH entries and display cleanly. No-op off Windows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ing zero strays) The live v0.6.18 run surfaced no strays despite dozens of stray uffs.exe copies on disk. Root cause: the daemon delivers search results in four shapes — inline rows, a memory-mapped rows file, an inline pre-formatted blob, or a memory-mapped blob (chosen by size + output shape). A real multi-hit Windows sweep returns a CSV/path *blob*, but the old code walked the JSON for `"path"` object keys, which only exist in the inline-rows case — so every blob/shmem result was silently dropped. Switch to the typed `search_cli` + `--columns path` (single-column output) and decode all payload variants via the client's shmem/blob helpers (read_search_results / stream_paths_blob_into), parsing the path-per-line blob (header + CSV quotes stripped). Windows-only module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…a cache The live v0.6.18 dry-runs grew a ~4 GB index cache because each 'y' to the coverage offer indexed more drives. That is by design (indexing is non-destructive and the deep sweep needs it), but the prompt gave no hint that saying yes builds an on-disk cache that persists even under --dry-run. Spell it out so the choice is informed. Windows-only module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… not a blind wait A freshly load_drive_letters-requested shard starts parked/cold and only becomes searchable once its body is resident. The previous fixed await_ready(120s) could return while shards were still loading, so the deep sweep searched a not-yet-ready index and found nothing. Poll status_drives until every requested drive reports a loaded tier (hot/warm) or the deadline elapses — so the sweep waits exactly as long as needed and never searches a still-parked shard. Best-effort: RPC errors keep polling to the deadline, then proceed with whatever is loaded. Windows-only module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…unning build The live Windows test ran an OLD uffs.exe: the shell prompt said the fix/uninstall-windows-followups branch was checked out, but the output still showed the pre-fix behaviour (off-path, \\?\ paths) — a stale binary never rebuilt/redeployed. The CLI's --version printed only the crate version, so there was no way to tell which build was running. The daemon already stamps UFFS_GIT_SHA into its startup log to close exactly this 'ran the wrong/stale binary' trap; port the same build.rs stamp to the CLI and surface it: `uffs --version` now prints 'uffs <ver> (<sha>[-dirty])'. Match the sha against `git rev-parse --short HEAD` to confirm the build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tring noise The live Windows sweep worked but listed non-binaries as removable strays: the daemon search matches `uffs.exe` as a *contains* query, so it also returned prefetch traces (UFFS.EXE-1234.pf), localized resources (uffs.exe.mui), checksums (uffs.exe.sha256), build recipes (uffs.exe.recipe), and NTFS alternate-data-stream entries (uffs.exe:com.dropbox.attrs). Add is_family_artifact: keep a hit only when its file name is exactly a family executable (uffs.exe / uffsd.exe / uffs-broker.exe / uffs-tui*.exe / …) or a cache file (*_compact.uffs / *_usn.cursor); drop anything ending in .pf/.mui/.sha256/.recipe or containing ':' (an ADS entry). Windows-only module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uffs name) Audit of the `uffs` -> `uffs.com` concern: every actual spawn site already uses a full current-exe-relative path, and Rust's Command appends .exe (never .com) on Windows — which is why the uninstall run completed correctly. The only bare names were the $PATH fallbacks in find_uffs_exe / find_daemon_exe, hit only when current_exe + sibling lookup both fail. Harden those to the platform binary name (uffs.exe / uffsd.exe on Windows) so a bare `uffs` can never be resolved to a legacy uffs.com via PATHEXT (.COM precedes .EXE) if the path is ever handed to a shell, a registry entry, or a logged command rather than spawned directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ystem check) On a machine that still has the predecessor C++ UFFS installed, the deep sweep listed its uffs.exe copies and — worse — version_strays ran each with --version, launching a GUI window per copy (the slow, 'CPP version keeps popping up' behaviour). The C++ product is a Windows GUI app; our Rust CLI is a console app. Read the PE Optional-Header Subsystem field (headers only, never executing the file): if a uffs.exe is a GUI-subsystem binary, drop it from the strays before probing. Only uffs.exe collides with the predecessor; the other family names are Rust-only and untouched. Windows-only module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…he top Add a 'uffs <ver> (<sha>) — uninstall' header to the dry-run and live output, so any captured run is unambiguously tied to the exact binary (the same stamp `uffs --version` shows). Makes a stale-binary run obvious at a glance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ogress The live 7-drive load took ~2.5 min but the readiness poll capped at 120 s, so it returned at 6/7 drives and the sweep searched a not-yet-ready index — missing the still-loading drive (D:). Raise the cap to 600 s and print 'indexing for the sweep: N/M drives ready...' as drives come online, so the wait covers a real cold multi-drive index and never looks like a hang. Windows-only module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#3/#4 from the Windows test. The load RPC blocks until the daemon finishes every drive (loaded sequentially), which overran the client timeout on a 7-drive system — so it returned at 6/7 (missed D:) and no progress ever showed. Fire the load on a background connection and poll status_drives on this thread, printing 'indexing for the sweep: N/M drives ready...' as drives come online — the poll, not the RPC return, decides when the drives are searchable, so a background timeout is harmless and the sweep no longer searches a partial index. Also reword the coverage prompt to be less repetitive. Windows-only module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lish #1: from a non-elevated terminal the broker (its LocalSystem service + process) cannot be stopped/removed, but the old gate refused the WHOLE uninstall. Mark the broker process as admin-only too, and instead of bailing, offer to skip the admin-only items and remove everything else now (or abort to re-run elevated). Adds RemovalPlan::drop_elevation_required. #2: show 'legacy' instead of '-' for versionless (old) binaries. #5: blank line between the 'found elsewhere' heading and the file list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Indexing every NTFS drive is a non-elevated, non-destructive read that the deep sweep requires, so it should just happen — not be a [y/N] choice. Drop the prompt: ensure_drive_coverage now always loads any not-yet-indexed drives (with the live N/M progress) and no longer takes a confirm callback. This also removes the elevated-vs-non-elevated output divergence: the runs only differed because one had drives already loaded (no prompt) and the other didn't (prompt). Now both just index what's missing. Windows-only module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…the core The live run left ~15 dev/diagnostic binaries in ~/bin untouched (analyze-diff, dump-mft-records, gen-hooks, uffs-bench, uffs-ci-pipeline, …) — a from-source / cargo install build drops them next to the core set. Add them to EXTRA_BINARY_STEMS so the install-dir sweep removes them. Refactor the deep sweep to derive its search patterns + family filter from the shared family set (KNOWN_BINARIES + EXTRA_BINARY_STEMS) instead of a second hardcoded list — so adding a binary in one place now updates both the install-dir removal and the cross-drive sweep. None of these are managed by --update, so KNOWN_BINARIES (the update set) is untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… running binary Three fixes from the live runs: 1. Decisions up front, no post-removal prompts: the broker keep/skip (elevation gate) and the deep-sweep strays opt-in are both decided before the single 'Proceed with removal?' go, then everything executes once into one combined outcome — so the summary + retry hint print once, not per-phase. 2. Platform-correct failure hint: elevation only exists on Windows (the broker is a LocalSystem service); a non-Windows uninstall is all user-land, so it no longer suggests 'sudo' there — a failure is a file in use. 3. The chicken-and-egg self-delete: the OS locks a running image, so deleting uffs.exe / uffs-update.exe in place is the 'access denied' seen in the live run. SystemEffects now skips the running self-binaries (matched verbatim- stripped, case-insensitive) and the existing spawned-cmd schedule_self_delete removes them after exit — the same deferred-delete installers (NSIS/Inno) use. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… handles it) In the elevated run, 'broker (pid 9064)' failed with exit 128: it was a StopProcess item executed via taskkill /F, but the broker is a LocalSystem service — taskkill can't stop it (and even forced, the SCM restarts it). The RemoveService item already stops + deletes it the right way (uffs_winsvc::stop + sc delete). Filter the broker out of the Processes group so it is never taskkill'd. Only the user-owned daemon / MCP remain there (no admin needed); the broker is handled solely by RemoveService. Removes the guaranteed-failure line from the outcome. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… deep sweep A non-elevated run should be told immediately that the broker needs Administrator — not after sitting through a multi-minute drive index + sweep. Move the elevation decision to right after the plan is shown, before platform_stray_plan: - Not elevated + broker installed: flag it and offer to continue (uninstall everything except the broker) or abort to re-run from an elevated terminal. - Elevated: skipped entirely — just remove everything (incl. the broker), and the running binary is self-deleted at the end as before. - Dry-run: only previews (the plan already marks the broker 'needs Administrator'). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… anchor the query The deep sweep's post-query step ran `<bin> --version` sequentially with no timeout, once per family hit. On a dev box (hundreds of `uffs*.exe` under `target/`) that took minutes, and any binary that doesn't cleanly handle `--version` (half-written artifact, something waiting on stdin) hung the whole sweep indefinitely. version_strays: - probe in parallel via a bounded `std::thread::scope` worker pool (cursor-stealing; no new dependency) - `probe_version_bounded`: stdin nulled, piped output, poll `try_wait` to a 2s deadline then `kill` — a hung binary goes unversioned instead of stalling DaemonSearch::find: - anchor each `stem.exe` query with `--name-only --ext exe` instead of a bare full-path substring. Measured 158 -> 46 hits for `uffs.exe`; identical to the exact-regex count, so it's lossless. Drops `.mui`/prefetch/ADS/path-substring noise at the daemon before rows cross the wire. Glob cache patterns untouched. Plus temporary `[sweep]` diagnostics (per-pattern raw/kept counts, phase timings, timeout count) routed through one `dbg_line` helper, to verify the live Windows run. To be removed once signed off. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, header, plain labels) The old output was two lines per binary (a `stem:` header + an indented row) with cryptic columns: ACTIVE/shadowed/off-path, a raw channel word (`unmanaged`), and a bare `-` scope. Redesign to a single aligned row per copy with a header and a STATUS legend: - ACTIVE -> `runs` (the copy a bare command executes, first on PATH); on-PATH-but-later -> `shadowed`; not-on-PATH -> `off PATH`. - Fold the channel + scope into one SOURCE column: `hand-placed` (was `unmanaged`), `dev build`, `winget (user)`/`winget (machine)` — scope only means something for winget, so the lone `-` is gone. - Columns are width-sized to header+cells; LOCATION is last / free-width. Display-only; resolution logic in resolve_order.rs is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t a racing hot-load) The old coverage fired its own load_drive_letters for any not-yet-loaded drive, racing the daemon's background startup load over the single-instance Access Broker pipe (ERROR_PIPE_BUSY). That churned the registry (observed 2/6 -> 0/6), intermittently dropped a drive (6/7), and could spin to the wait cap. Replace it with the proven CLI flow, only when needed: - Managed set = every `status_drives` row (any tier — hot/warm/parked/cold; a search re-promotes a parked drive on demand). - If the daemon already covers every system drive: do nothing. - If ANY drive is missing: `uffs --daemon kill`, wait for full shutdown, then a clean `uffs --daemon start` (loads every drive with broker warm-up, returns only once Ready), then poll until coverage is complete. No `restart`, no hot-load, no competing loader thread. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…de gracefully Spawning `uffs --daemon start` as a child of the uninstall intermittently hangs the drive load (daemon stuck at 5/7, zero progress) even though a standalone `uffs --daemon start` loads all drives in seconds. Rather than chase that spawn-context bug, stop bringing the daemon up in-process: - Fully covered already (warm daemon): proceed silently — the common case. - Daemon up but mid-load: wait briefly (60s cap), then proceed with whatever loaded. Never blocks indefinitely. - No daemon reachable: print a one-line notice telling the user to run `uffs --daemon start` and re-run for a complete sweep; continue best-effort. No kill, no start, no restart, no competing loader — the deep sweep now covers whatever the daemon already has and never hangs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(kill+start in-process)
Previous attempt shelled out to `uffs.exe --daemon start` as a subprocess, which
spawned the daemon as a grandchild and intermittently hung its drive load
(stuck at 5/7). A standalone `uffs --daemon start` loads all drives in seconds.
Reuse the exact handlers the CLI dispatches instead: call
`daemon_mgmt::daemon(&DaemonAction::Kill)` then `daemon(&Start{..})` in-process,
so the daemon is a DIRECT child of this process — identical topology to a shell
`uffs --daemon start`. When coverage is complete, no-op silently; when a drive
is missing, kill, wait for full shutdown, start (blocks until Ready = all drives
loaded), then proceed. Any handler error is best-effort: note it and sweep with
whatever is loaded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…binaries Only uffs.exe and uffs-update.exe carried Windows PE resources; uffsd, uffsmcp, uffs-broker, and uffs-mft shipped bare. A metadata-less unsigned binary is both unbranded and a mild antivirus ML false-positive signal (all 7 tripped the same generic Defender heuristic on the 0.6.18 release). Add a per-crate build.rs to each (mirroring uffs-cli/uffs-update) that embeds via winresource on MSVC-Windows only: - the UFFS icon (shared assets/brand/icons/uffs.ico) - version info: ProductName, FileDescription, CompanyName, LegalCopyright, OriginalFilename (winresource auto-fills File/ProductVersion from the crate) - a new shared assets/brand/app.manifest (asInvoker, PerMonitorV2, longPathAware) winresource added as a build-dependency of each crate. No-op off Windows; the uffs-mft library target is unaffected. Validated with cargo xwin clippy for x86_64-pc-windows-msvc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Branding consistency across the whole family: the diagnostic + CI tools shipped bare (no icon/version info). Add a per-crate build.rs embedding the shared UFFS icon + version info + app.manifest via winresource (MSVC-Windows only, no-op elsewhere) to the 6 crates that produce them: - uffs-diag (9 bins: analyze-diff, analyze-mft-parents, compare-raw-mft, compare-scan-parity, cross-check-mft-reference, dump-mft-extents, dump-mft-records, inspect-mft-record-flow, scan-mft-magic) - uffs-bench (uffs-bench) - scripts/ci/gen-hooks, scripts/ci/gen-workflow, scripts/ci/manifest-audit - scripts/ci-pipeline (uffs-ci-pipeline) winresource added as a build-dependency of each. Validated with cargo xwin clippy for x86_64-pc-windows-msvc. Completes the all-binaries icon-branding task. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t (fixes fresh-load hang) Root cause of the daemon's fresh (no-cache) load hanging at 5/6-of-7 drives: after the main MFT read, each drive re-reads the $UpCase table via read_handle_at -> read_handle_at_once, which issued an overlapped ReadFile on the broker's FILE_FLAG_OVERLAPPED handle with a NULL-hEvent OVERLAPPED and then GetOverlappedResult(bWait=true). With no event, GetOverlappedResult waits on the file object itself; the broker vends duplicate handles to the same volume file object, so under a concurrent multi-drive load it cannot tell which read completed and blocks FOREVER (Microsoft's documented pitfall). Debug logs showed 1-2 drives stall right after "Adopted Access Broker volume handle", never reaching "Parsed $UpCase data runs" — a silent, error-less hang. This affected every fresh `uffs --daemon start`, not just the uninstall. Fix: bind each read to a dedicated manual-reset event and wait on the event (never the shared handle), bounded by IOCP_WAIT_COMPLETION_DEADLINE. On timeout, CancelIoEx + drain, then return a retryable ERROR_OPERATION_ABORTED so the existing read_handle_at retry loop reissues it. The event makes the wait specific to this operation (the documented fix); the bound guarantees the load can never hang forever again — a genuinely wedged read fails fast and the daemon reaches Ready instead of stalling. Validated with cargo xwin clippy (x86_64-pc-windows-msvc). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y, -v for detail The interactive flow was noisy and the elevation choice confusing: the full binary table, inventory, and plan (broker item included, "(needs Administrator)") printed BEFORE a double-clause elevation question, and answering y echoed "Leaving the broker installed" mid-flow. Restructure to: decide -> gather -> summarize -> confirm. - Elevation is THE FIRST question (right after the header, before any analysis output): list what needs an Administrator terminal and why, then one clear choice — continue without it, or abort to re-run elevated. `--yes` continues without asking. Declined items are dropped from the plan entirely, so the summary never shows work that will not happen. - Default output is compact: a one-line scan summary replaces the resolution table + inventory; the `[sweep]` diagnostics are silent. New `-v/--verbose` restores the full tables and sweep detail (dbg_line now verbose-gated). - The removal plan prints ONCE, at the very end after the deep sweep, as the final "here is what this run will do" — followed by a "NOT removed in this run (needs Administrator)" note for anything skipped at the gate, then the strays opt-in and the single final confirmation. - Dry-run keeps the complete preview (admin markers intact) plus a note that a real non-elevated run asks up front. drop_elevation_required now returns the dropped items' descriptions for the summary note. Validated with cargo xwin clippy (prod + tests) and the uninstall unit tests (52 passed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…al time
The elevation gate previously offered only continue-without or abort. Windows
cannot elevate a running process in place, so add the middle path the user
actually wants: elevate exactly what needs it, exactly when it is needed.
- Gate becomes a 3-way choice on Windows (still the FIRST question):
e = elevate at removal time (one UAC prompt), c = continue without,
a = abort (default). Non-Windows keeps the binary continue/abort. `--yes`
still means continue-without — a scripted run must never pop a surprise UAC.
- Choosing `e` only records the decision: the admin items stay in the plan
(final summary notes "will show one Windows UAC prompt when removal starts"),
and the whole flow continues in the same window. Nothing elevates before the
final confirmation.
- At execution, SystemEffects::remove_service routes through a one-shot
elevated helper: PowerShell `Start-Process -Verb RunAs -Wait -PassThru`
relaunches uffs.exe in the hidden `--uninstall --remove-service-helper
<name>` mode (refuses to run non-elevated; performs the exact same
stop+delete as the elevated in-process path), then verifies the service is
actually gone. Keeps the crate unsafe-free per the module's shell-out design.
- A declined UAC prompt (catch -> exit 223) degrades gracefully: the item is
reported as skipped with the elevated re-run hint, everything else is still
removed, and no mid-execution question is asked (decisions stay up front).
- The helper never touches binaries, so there is no self-delete race with the
waiting parent process.
Validated with cargo clippy (host) + cargo xwin clippy (Windows, prod + tests);
uninstall unit tests pass (38), including the hidden-flag parse test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y final choice
Three UX upgrades to the interactive flow (decide -> gather -> present -> confirm):
1. No wasted wall-clock: the drive-coverage reload + deep sweep start on a
background thread the instant `--uninstall` fires, overlapping the elevation
question. The daemon handlers gain a quiet mode (daemon_mgmt::daemon_quiet,
RAII-reset static) and coverage defers its narration as notes, so background
work never prints over the prompt. After the gate, a small spinner
("Gathering artifacts (indexing the drives / searching the drives)...")
runs until the gather finishes. `-v` keeps the sequential, live-printing
flow for diagnosis; a panicked gather degrades to "no strays".
2. Nothing is shown until everything is gathered, then the COMPLETE picture
prints at once as two aligned table sections: CORE (the install — binary
resolution table + data/cache inventory) and EXTRA (deep-sweep strays, now
a BINARY / VERSION / LOCATION table matching CORE's shape), followed by the
action plan and the gate notes.
3. The two trailing questions collapse into one 3-way tied to the sections:
a = ALL (CORE + EXTRA), c = CORE only, q/Enter = ABORT.
Without EXTRA files it stays the classic "Proceed with removal? [y/N]".
`--yes` still means ALL. Execution extracted into execute_all (no prompts
past consent).
The quiet-mode plumbing pushed daemon_mgmt.rs past the 800-LOC budget; fixed
at the root by decomposing, not excepting: the read-only status/stats rendering
(daemon_status, print_drive_line, tier_marker, print_not_running, daemon_stats,
compute_hit_rate_percent) moves to the new sibling commands/daemon_status.rs
(254 LOC), leaving daemon_mgmt.rs at 625 LOC with dispatch, the elevation gate,
and the mutating handlers. Display code unchanged byte-for-byte.
Validated with cargo clippy (host) + cargo xwin clippy (Windows, prod + tests);
44 unit tests pass; file-size gate passes with no new exception.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the [diag] leak
Follow-ups from the live Windows dry-run of the new flow:
- Presentation order: the data/cache/config inventory + broker-service state
now print right after the coverage note, BEFORE the CORE binary table (was
sandwiched between CORE and EXTRA).
- The removal-plan summary now includes the deep-sweep findings instead of
silently omitting them: a "Found elsewhere (EXTRA)" group line ("N UFFS
file(s) outside the standard install locations (listed above; removed only
with ALL)") and a reclaim line that reads "~X across N CORE item(s), plus M
EXTRA file(s) with ALL" — alluding to the ALL/CORE/ABORT question the real
run asks. "Nothing to remove" now only prints when BOTH plans are empty.
- The daemon_start [diag] spawn-chain dump is also silenced in quiet mode: with
UFFS_LOG=debug set it printed from the background reload straight over the
spinner.
- Dry-run keeps its established gate behavior (no elevation question; markers +
the explanatory note), confirmed as the intended design.
Validated with cargo clippy (host) + cargo xwin clippy (Windows); 44 unit
tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… prompts Blank line between the elevation gate's intro and its item list, and before the "Choice [e/c/A]:" and "Choice [a/c/Q]:" lines, so the questions stand apart from their option lists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…LOG/Output The first real Windows run surfaced a cluster of execution bugs. All fixed at the root: 1. Teardown-last plan order (the user-prescribed sequence): tool binaries -> PATH -> "Shutdown (stopped last)" (daemon stop + broker service removal) -> data/cache/config (a running daemon holds handles inside them) -> "Runtime binaries (after shutdown)" (uffsd/uffs-broker/uffsmcp/uffs-mcp-http, whose images are locked while running) -> deferred self-delete of uffs.exe + uffs-update.exe. The working tools stay usable for the whole run. 2. One locked file no longer traps a whole directory: delete_binaries is now best-effort across the set (the original run lost 21 deletions to one lingering uffsd.exe), with a 750ms settle-and-retry pass, reporting exactly which files failed. 3. The daemon stop re-discovers the CURRENT daemon via the real `uffs --daemon kill` handler (pid file/socket) instead of the analyzed pid (stale after the deep sweep's coverage reload), then waits for IPC-down + image release before the runtime binaries are deleted. 4. Windows daemon-management elevation gate now mirrors the Unix owner gate: no elevation needed when there is no PID file (nothing to protect) or when the daemon's launch-state sidecar records a NON-elevated launch (the daemon now writes an "elevated" flag into daemon.state.json) — a user-level daemon is the user's to kill even with the broker gone. Falls back to the broker probe otherwise. 5. The deferred self-delete never actually deleted anything: std's Windows arg quoting backslash-escaped the `del "path"` quotes inside the `cmd /c` payload, which cmd.exe does not parse. Passed verbatim via raw_arg now. 6. Formatting: the coverage failure notes put "Continuing the deep sweep..." on its own line, and a blank separator precedes the `[sweep]` block (-v). 7. `uffs-broker --install` narrates its steps (sc create -> ok, sc start with a "can take a minute" note -> ok) instead of a silent minute-long wait. plan.rs crossed the 800-LOC budget with the reorder; fixed by decomposing (the established sibling-tests pattern): its unit tests moved to plan/tests.rs, leaving plan.rs at 513 LOC. Tests updated to the new order contract plus a regression test pinning the runtime-stem split. Validated with cargo clippy (host) + cargo xwin clippy (Windows, prod + tests); 38 uninstall tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…0.10.2 + full cargo-vet coverage Version bumps in workspace Cargo.toml: rmcp 1.8.0 -> 2.1.0 (MCP 2025-11-25 spec), aes-gcm 0.10 -> 0.11.0, rand 0.10.1 -> 0.10.2, indicatif 0.18.4 -> 0.18.6, plus the transitive lockfile fallout. Supply-chain: 17 newly-unvetted crates cleared with real audits and publisher trust, no exemption bumps. Ten RustCrypto crates (aead, aes, cipher, cmov, cpubits, ctr, ctutils, ghash, polyval, universal-hash) are covered by trusted-publisher entries for the RustCrypto GitHub org accounts, consistent with the existing digest/hybrid-array precedent. Six deltas were reviewed line-by-line and certified (notes in supply-chain/audits.toml). cargo vet prune dropped the superseded exemptions; cargo vet --locked passes. Vet-Reviewed-Diff: rmcp@1.8.0->2.1.0 Vet-Reviewed-Diff: rmcp-macros@1.8.0->2.1.0 Vet-Reviewed-Diff: aes-gcm@0.10.3->0.11.0 Vet-Reviewed-Diff: rand@0.10.1->0.10.2 Vet-Reviewed-Diff: console@0.16.3->0.16.4 Vet-Reviewed-Diff: indicatif@0.18.4->0.18.6 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… rmcp 2.x APIs aes-gcm 0.10 -> 0.11 (crates/uffs-security/src/crypto.rs): GenericArray is gone (hybrid-array now); AeadInPlace is deprecated in aead 0.6, so move to AeadInOut: encrypt_in_place_detached -> encrypt_inout_detached with InOutBuf, decrypt likewise. Tag slice conversion is now fallible (try_into + bad_data) instead of the old panicking from_slice. On-disk format unchanged; v1/v2 round-trip and compatibility tests pass (49/49). rmcp 1.8 -> 2.x (crates/uffs-mcp): model reorg renames. Content -> ContentBlock, RawContent::resource_link(..).no_annotation() -> ContentBlock::resource_link(..), RawResource/RawResourceTemplate -> Resource/ResourceTemplate (annotations are plain Option fields now, so .no_annotation() disappears), PromptMessageRole -> Role, and the Annotated wrapper's .raw field access flattens away in tests. The ResourceContents match in mcp_protocol.rs names BlobResourceContents explicitly (wildcard_enum_match_arm). All 99 uffs-mcp tests pass. Known leftover: rmcp 2.x deprecates model::Root (SEP-2577); roots.rs carries a scoped #![expect(deprecated)] with reason until the Roots migration follow-up on this branch. lint-prod and lint-tests both pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # crates/uffs-cli/src/commands/daemon_mgmt.rs # crates/uffs-cli/src/commands/uninstall/coverage.rs # crates/uffs-cli/src/commands/uninstall/effects.rs # crates/uffs-cli/src/commands/uninstall/mod.rs # crates/uffs-cli/src/commands/uninstall/plan.rs # crates/uffs-cli/src/commands/uninstall/plan/tests.rs # crates/uffs-cli/src/commands/uninstall/remove.rs # crates/uffs-cli/src/commands/uninstall/render.rs
…just-go since #346) The anti-pattern gate (a `just go`-lane check, not in pre-push/PR CI) flagged 13 from_utf8_lossy/from_utf16_lossy sites accumulated since its bytes rules landed (2026-06-04, #346) — a month of uninstall/update/broker code shipped through lanes that never run it. Site-by-site treatment, no blanket waivers: True fixes (bytes feed real decisions): - uffs-mft platform/process.rs: image path via OsString::from_wide — lossless (Windows paths are arbitrary u16s), so path comparisons are exact. - uninstall/sweep.rs ShmemBlob: strict from_utf8 — these paths feed the EXTRA delete list; a corrupt blob is rejected outright, never lossy-mangled toward a delete. (The daemon always emits valid UTF-8.) - update/acquire.rs latest_version: strict from_utf8 — our own helper emits ASCII `latest=` lines; non-UTF-8 means something is wrong -> None. AUDIT-OK(bytes) with per-site safety arguments (house style, cf. uffs-client): - version probes (sweep.rs + binaries.rs, 4 sites): U+FFFD cannot fabricate an ASCII MAJOR.MINOR.PATCH token; lossy only fails toward "legacy"/None. - broker sc_output (2): operator-facing diagnostic text, never parsed. - procinfo (4): pgrep pids (lossy cannot create digits), Windows console tools emit OEM/ANSI bytes (strict parse would break localized systems), kernel cmdline + ps field bytes carry no encoding guarantee — best-effort display/probe text throughout. Gate: ✅ no forbidden patterns. Full `just go` green (tests, doc-tests, prod + test lint, dependency security, rustdoc links). cargo xwin clippy (Windows) clean for the from_wide change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… hook
SEP-2577 deprecates the MCP Roots capability upstream with NO replacement API
(workspace context moves to explicit tool inputs); rmcp 2.x marks Root +
Peer::list_roots deprecated with removal planned. UFFS's roots -> NTFS-prefix
search scoping is real, working functionality that clients still exercise, so
dropping it now would regress scoping for Roots-advertising clients.
The for-good fix is architectural: make the feature survive the API's removal.
- roots.rs is now transport-agnostic: new AdvertisedRoot { uri, name } replaces
every rmcp::model::Root reference (resolve_root, update_roots_state, all 11
tests) — the module-level #![expect(deprecated)] is GONE.
- handler/mod.rs's on_roots_list_changed is the single remaining place that
speaks the deprecated wire API: one scoped #[expect(deprecated)] around the
list_roots call + the Root -> AdvertisedRoot conversion, with the exit plan
in the comment: when rmcp removes the API, delete this hook — scoping then
flows from the explicit path filters the search tools already accept.
Deprecation surface: whole module -> ~10 boundary lines with a documented
deletion path. 98 uffs-mcp tests pass; full `just go` green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First tranche of the exemption long-tail (332 -> 319; fully-audited 158 -> 171).
Every audit is a real source review — capability sweep (fs/net/process/env/
build.rs), every unsafe block read against its invariant, publisher records
verified on crates.io — with the findings in each audits.toml note:
Full audits (exemption removed for each):
- itoa 1.0.18 dtolnay bounded table reads, ASCII-only utf8_unchecked
- ryu 1.0.23 dtolnay 24-byte buffer vs proven <=24-byte max write
- unicode-ident 1.0.24 dtolnay trie get_unchecked covered by exhaustive char test
- semver 1.0.28 dtolnay tagged-pointer small-string invariants verified,
parser validates ASCII before every new_unchecked
- serde_path_to_error dtolnay zero unsafe, zero capability, pure serde adapter
0.1.20
- scopeguard 1.2.0 Amanieu ManuallyDrop/ptr::read pairs read-once-each
- same-file 1.0.6 BurntSushi is_std Drop leaks std fds (no double-close)
- winapi-util 0.1.11 BurntSushi every Win32 call: sized out-params + rc checks
- thiserror 1.0.69 dtolnay build.rs = canonical $RUSTC feature probe (read fully)
- thiserror-impl 1.0.69 dtolnay zero real unsafe, pure token transform
- async-trait 0.1.89 dtolnay zero real unsafe, no unsafe emitted
Delta audits (the workspace also carries thiserror 2.x):
- thiserror 1.0.69 -> 2.0.18 no_std rework; build.rs writes a static-template
OUT_DIR shim + rustc --version gate; no new capability
- thiserror-impl 1.0.69 -> 2.0.18 zero unsafe/capability in added lines
`cargo vet --locked` -> Vetting Succeeded. No trust-entry shortcuts: an earlier
blanket `cargo vet trust` pass was reverted in favour of these source audits.
Vet-Reviewed-Diff: thiserror@1.0.69->2.0.18
Vet-Reviewed-Diff: thiserror-impl@1.0.69->2.0.18
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Dependency modernization with full supply-chain rigor, plus the hardening work that surfaced along the way.
Dependency bumps (all with complete cargo-vet coverage)
Breaking-API migrations (verified)
AeadInPlace→AeadInOut, GenericArray → hybrid-array, fallible tag conversion. On-disk format unchanged — v1/v2 round-trip + compatibility tests passContent→ContentBlock,Annotated<Raw*>unwrapping,RolerenameSEP-2577 Roots — fixed for good
SEP-2577 deprecates MCP Roots with no replacement API.
roots.rsis now fully transport-agnostic (newAdvertisedRoottype; module-level#![expect(deprecated)]gone); exactly one scoped expect remains at the wire boundary with its deletion plan documented — when rmcp removes the API, one hook is deleted and scoping continues via the explicit path filters the tools already accept.Anti-pattern gate debt cleared (13 sites)
First
just gosince the bytes rules (#346) landed. 3 true fixes (losslessOsString::from_widefor Windows image paths; strictfrom_utf8where bytes feed the EXTRA delete list and the self-updatelatest=parse) + 10AUDIT-OK(bytes)with per-site safety arguments.cargo-vet exemption burn-down (332 → 319; fully audited 158 → 171)
11 full source audits (itoa, ryu, unicode-ident, semver, serde_path_to_error, scopeguard, same-file, winapi-util, thiserror, thiserror-impl, async-trait) + 2 delta audits (thiserror 1.0.69 → 2.0.18 pair). Every unsafe block read against its invariant, capability sweep per crate, publishers verified on crates.io. An earlier blanket
cargo vet trustpass was reverted in favour of these source audits.Also
cargo update, never blanket)Validation
cargo vet --locked→ Vetting Succeeded · vet-audit-discipline gate green · fulljust gogreen (tests, doc-tests, prod+test lint, dependency security, anti-pattern gate, rustdoc) · cargo xwin clippy (Windows) clean · 49 uffs-security + 98 uffs-mcp + full workspace tests pass🤖 Generated with Claude Code