Windows mount via WinFsp; fix the broken CI workflow YAML - #1
Merged
Conversation
…rtup The CI has been failing to *start* on every push for a while — 0-second "failure" runs with no jobs, GitHub's "workflow file issue" — including commits that never touched ci.yml (the README docs commits). The committed ci.yml does not parse as YAML: three corruptions in the miri-and-loom job, all pre-existing in the remote main (`git show HEAD:.github/workflows/ci.yml` fails to parse): - a comment lost its leading `# ` and first line (`\n on the unit tests of the crates...`), breaking the block structure at line 193; - two step names contain an unquoted `: ` (`Miri (memory and wire: leak check on)` and `Miri (runtime: ...)`), which YAML reads as a nested mapping. Restore the comment and quote the two names. `python3 -c "yaml.safe_load(...)"` now parses the file — 5 jobs (gates, windows-lint, callgrind, miri-and-loom, windows-nightly). This unblocks the whole pipeline, including the new Windows WinFsp mount job, none of which had actually been running. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…p-x64.dll The WinFsp mount test BUILT and LINKED correctly on the native Windows runner (the hand-transcribed FFI is proven — the exe compiled against winfsp-x64.lib), but the process failed to start with STATUS_DLL_NOT_FOUND (0xc0000135): the loader could not find winfsp-x64.dll at run time. The DLL lives in C:\Program Files (x86)\WinFsp\bin, which isn't on the default PATH (linking used the .lib via WINFSP_LIB; running needs the .dll). Append the bin directory to GITHUB_PATH after the choco install so every later step — the mount test — can load it. This is a loader/PATH fix, not a mount-logic change: the FFI, vtable, and callback build is confirmed by the successful compile+link on real Windows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Exceeded on CI)
With CI restored (the YAML fix), the macOS + Linux gates surfaced a pre-existing
failure: the client/server live-daemon tests panic with `Refused(BudgetExceeded
{ available: 0 })`. Root cause (not a regression — pre-existing, environmental):
slates mlocks its RAM for hermeticity (RAM-only, no swap, R1), so the daemon's
per-shard provisioning reserve is derived from RLIMIT_MEMLOCK (`profile.lock.bytes`
→ `region_bytes(lock/shards/classes)`, §4.2). CI runners default to a tiny mlock
limit, so the reserve rounds to ~0 and every provision (even a 1 MiB volume)
refuses.
The design-consistent fix is to grant the process enough lockable RAM, exactly as
a real slates deployment does (like a database that mlocks its buffer pool). Raise
`ulimit -l` before the daemon-spawning steps (the workspace Test and the CLI flow),
best-effort (`|| true`) where a runner already permits unlimited or does not honor
the soft limit. No code change — the mlock-bounded capacity is intended behavior.
The Windows nightly Test uses a different working-set mechanism (no `ulimit`), left
as-is. Whether the daemon should also derive a small-mlock *floor* or degrade when
locking is refused is a separate §4.2 design question for Ada.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Exceeded)
Fixing the CI YAML exposed a real bug: on a machine where the OS grants little
lockable RAM (a container, an old default RLIMIT_MEMLOCK=64KiB, a CI runner), the
daemon refused EVERY volume provision — even 1 MiB — with `BudgetExceeded {
available: 0 }`. The client and anchor live-daemon tests failed on both the macOS
and Linux gates.
Root cause: the per-shard provisioning reserve was derived from the OS lock limit
(`region_bytes(profile.lock.bytes, shards, classes)` in config.rs), which feeds the
store budget and the arena size (daemon.rs). A tiny lock limit rounds the reserve
to ~0. But the arena is NOT locked by default — locking happens only when a client
asks (`require_locked`, verbs.rs), and the mem lock sequence keeps an unlocked
region "usable, unlocked, and counted" (mem/src/lock.rs, D-12 honest degradation).
The design's §4.2 boot-order failure matrix names this exact case — "a locked-down
CI container refuses mlock; lock capacity 0" — as something to degrade through, not
refuse. Capping the DEFAULT budget at the lock LIMIT contradicts the design.
Fix: derive the default reserve from usable memory
(`region_bytes(profile.facts.memory.available, …)`); the lock limit is unchanged
where it belongs — a `require_locked` volume still calls `arena.lock()`, best-effort.
`region_bytes`'s parameter/derivation note generalized from "lock capacity" to
"memory capacity" (its one real caller is this reserve; the other `region_bytes`
name is an unrelated method). On a machine that can lock freely the two bases are
the same (the probe already records memory.available when RLIMIT_MEMLOCK is
unlimited). Full write-up: docs/bugs/2026-09-10-provisioning-budget-from-mlock-limit.md.
Reproduced and verified under `ulimit -l 64`: the client (3), anchor (6) and
server nfs_mount (4) suites go from `BudgetExceeded { available: 0 }` to passing;
budget unit tests and normal runs unchanged; xtask check clean. Reverted the inert
`ulimit -l unlimited` CI lines from the prior commit (the runners ignore it) so CI
runs under the constrained limit and proves the code fix.
Co-Authored-By: Claude Opus 4.8 <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.
Opened to run CI on the Windows WinFsp mount — and to fix the CI itself, which has been failing to start on every commit (0-second "failure", no jobs) because the committed
ci.ymldoes not parse: three pre-existing corruptions in the miri-and-loom job (a comment that lost its#, two step names with an unquoted:).git show HEAD:.github/workflows/ci.yml | yaml.safe_loadfails. This branch restores it (parses to 5 jobs), unblocking the whole pipeline.The Windows work (already on main, verified here by CI)
crates/bridge-winfsp):FSP_FILE_SYSTEM_INTERFACEvtable +FspFileSystem*FFI hand-transcribed from winfsp's headers, 16 callbacks onto the sharedBridgeover a single owner thread holding the!Sendvolume (bounded channel, noMutex/Arc), serialized by the COARSE guard. Thewindows-latestjob installs WinFsp and runs a live mount (WINFSP_TEST_MOUNT=1): mount a volume at a drive letter through the real kernel FSD, create/write/read/list/delete through the Windows filesystem, unmount.Local gates (all clean)
cargo fmt --check,xtask check(structural/literals/unsafe), macOS + Linux + Windows cross-lint clippy-D warnings, macOS tests.🤖 Generated with Claude Code