Rust core (shed-core)¶
The shed-server protocol client — HTTP + SSE, control-token auth, TLS pinning,
and the wire DTOs — lives in a shared Rust core under core/, so the same logic
backs both the macOS Swift app and the Tauri cross-platform client (the shipped
Linux client) without being re-implemented per language. See plans/phase-1-rust-core.md
and plans/phase-2-rust-clients.md for the plans + panel reviews.
Layout¶
A cargo workspace under core/ (conventions mirror ../roost) — its members
(shed-core, shed-core-ffi, shed-app, shedctl) are all default-members:
shed-core— a pure Rust lib (no UniFFI): wire DTOs + serde decoders (models.rs), the reqwest(rustls) client (http.rs), the SSE parser (sse.rs), leaf-cert pinning (tls.rs), and the control-token FSM (token.rs), plus aconfigparser and a pull-basedcreateorchestration store — and nowrc.rs(pure Remote-Control: the pane classifier, theshed-ext-rc+ non-interactive SSH argv builders, and the wire DTOs). The Linux clients (the Tauri app,shedctl) link this crate directly (no UniFFI).shed-core-ffi— a thin UniFFI wrapper (crate-type = ["staticlib"]) exposing aShedCoreobject + records to Swift.scripts/build-core.shbuilds it, runsuniffi-bindgen, and assembles a staticShedCoreFFI.xcframeworklinked into the app's Mach-O — no new dylib, so the release signing/notarization path is unaffected.shed-app— the UI-free app-logic layer (Backend), a workspace default-member; consumed by the Tauri client (as a cross-workspace path dep). Holds theRcRunnerportability seam (rc.rs, behind the non-defaultrc = ["tokio/process"]feature) — the trait where a future mobile in-process-SSH runner replaces the desktop subprocess runner, so oneRcServiceserves every frontend.shedctl— a headless UDS/IPC client shipped in the.debalongside the Taurished-desktopbinary, mirroring the macOS Swiftshedctl. It is indefault-members, somake core-test/core-lintcover it on the Mac.- Tauri client — not a
core/workspace member, but a consumer and the shipped Linux client:tauri/src-tauriis its own standalone cargo workspace that takesshed-core+shed-appas cross-workspace path deps. Its[[bin]]isshed-desktop-tauri, installed to/usr/bin/shed-desktopin the.deb(built via nfpm,linux/scripts/build-deb.sh). Runs on macOS (WKWebView, a UI-comparison loop vs the Swift app) and Linux (WebKitGTK, the shipped target) — seeplans/tauri-phase-{a,b,c}.md.
make core builds it; make build / make bundle / CI build it before any
SwiftPM step (the .binaryTarget path must exist first). The generated artifacts
under core/artifacts/ are gitignored.
How the Swift app uses it¶
ShedServerClient (ShedKit) delegates to the core when SHED_DESKTOP_RUST_CORE=1
and otherwise keeps its existing URLSession path. RustShedCoreAdapter maps the
Rust records to the app's Swift Models (which double as the IPC wire shapes),
so every IPC op stays byte-unchanged. identify reports core: rust|swift.
The Rust core is the macOS default (Phase 2 M0); SHED_DESKTOP_RUST_CORE=0
forces the Swift path (a rollback escape hatch kept ≥2 releases). A per-host
adapter-construction failure fails loudly rather than silently downgrading. CI
runs the e2e suite on the default (rust) plus a =0 Swift-fallback leg, and a
golden-JSON cross-backend byte-diff guards parity; identify.core reports which
backend is active.
Invariants preserved (parity with the Swift client)¶
- Defensive decoding:
{"sheds": null}→[], omitted optionals, lenientShedStatus,"?"name sentinels, and timestamps carried verbatim (parsing stays in Swift). - Auth, fail-closed: a provider mint failure sends no token (never the static one — no secure-by-default downgrade); a 401 invalidates + retries once (provider-backed only); create mints once with no retry.
- TLS: leaf SHA-256 pin (
sha256:<lowercase-hex>), fail-closed on a non-https://URL. - SSE create: cross-chunk framing, and the
error/ stream-ended-without-completeterminal semantics.
Building & testing¶
make core # build the staticlib + regenerate the xcframework
make core-test # cargo test the workspace
make core-lint # cargo clippy -D warnings
make test # swift tests (builds the core first)
SHED_DESKTOP_RUST_CORE=1 make e2e-ci # the hermetic e2e with the Rust backend
Rust changes need cargo/rustup (the workspace pins the channel in
core/rust-toolchain.toml).
Status & scope¶
Phase 1 (this core) covers the read client, lifecycle, SSE create, TLS pinning,
and the control-token FSM. Phase 2 (done) made it the macOS default (with a
golden-JSON cross-backend byte-diff + a size/cold-launch budget), got shed-core
building/testing on Linux, and hoisted the create orchestration + a config parser
into it. Phase 3 (done) first shipped a Linux .deb via charliek/apt-charliek
(apt install shed-desktop) with a bundled shedctl and unified the macOS + Linux
functional suites into one tools/shedtest harness; the release pipeline is
create-release → mac + linux → apt-charliek dispatch (see RELEASING.md). The
Tauri cross-platform client (tauri/), built on shed-core + shed-app, then
took over as the shipped Linux client — the earlier GTK MVP has been retired and
the .deb is now built from tauri/src-tauri (WebKitGTK). It carries lifecycle +
create, the approval spine (polkit gate + zbus notifier on Linux, Touch-ID on macOS),
the Agents/RC pane, tray/native-menu, and launch-at-login, driven by the
tools/shedtest --target mac|tauri harness. Deferred to plans/phase-4-rust-core-only.md:
retiring the Swift URLSession path + unifying config via the FFI. Still deferred:
absorbing/rewriting the credential broker in Rust (the final consolidation). See
plans/tauri-phase-{a,b,c}.md.