Skip to content

Windows mount via WinFsp; fix the broken CI workflow YAML - #1

Merged
adalundhe merged 4 commits into
mainfrom
winfsp-mount
Sep 10, 2026
Merged

Windows mount via WinFsp; fix the broken CI workflow YAML#1
adalundhe merged 4 commits into
mainfrom
winfsp-mount

Conversation

@adalundhe

Copy link
Copy Markdown
Member

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.yml does 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_load fails. This branch restores it (parses to 5 jobs), unblocking the whole pipeline.

The Windows work (already on main, verified here by CI)

  • WinFsp mount host (crates/bridge-winfsp): FSP_FILE_SYSTEM_INTERFACE vtable + FspFileSystem* FFI hand-transcribed from winfsp's headers, 16 callbacks onto the shared Bridge over a single owner thread holding the !Send volume (bounded channel, no Mutex/Arc), serialized by the COARSE guard. The windows-latest job 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.
  • slates-rt on Windows (IOCP + AFD readiness), completion transport, cross-process wake, rendezvous, the daemon (RAM-only IPC), and the Node/Python async SDK bindings.

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

adalundhe and others added 4 commits September 9, 2026 20:53
…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>
@adalundhe
adalundhe merged commit 1f40689 into main Sep 10, 2026
3 of 6 checks passed
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