Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,10 @@ registered mirror) rather than accepting a partial reset.

The exported TypeScript mount launcher currently models one `remotePath` and
rejects `RELAYFILE_MOUNT_PATHS_FILE` before creating directories or spawning a
process. Use the `relayfile` CLI directly for multi-path mounts until the
TypeScript session and status types represent multiple roots.
process. The `relayfile` CLI is also single-root while scoped mount operator
surfaces are unavailable, so configure one remote path only. Multi-root
configuration will return when the TypeScript session and CLI operator surfaces
represent every scoped child root.

The FUSE layer caches file content in kernel memory independently of its attribute TTL. By default content is held for 30 seconds and attributes for 2 seconds. Tune with `--fuse-content-ttl` (or `RELAYFILE_MOUNT_FUSE_CONTENT_TTL`) if your workload needs fresher reads or can tolerate a longer cache window:

Expand Down
34 changes: 34 additions & 0 deletions cmd/relayfile-cli/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,40 @@ func TestResolveCLIRequestedLocalLayoutRefusesAllScopedRoutes(t *testing.T) {
}
}

func TestMountRefusesRepeatedRemotePathsWithScopedLayoutBeforeSideEffects(t *testing.T) {
t.Setenv("HOME", t.TempDir())
clearRelayfileEnv(t)

localRoot := filepath.Join(t.TempDir(), "mirror")
var requests atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests.Add(1)
http.Error(w, "scoped mounts must be rejected before contacting the server", http.StatusInternalServerError)
}))
defer server.Close()

err := run([]string{
"mount", "ws_demo", localRoot,
"--server", server.URL,
"--token", testJWTWithWorkspace("ws_demo"),
"--remote-path", "/github",
"--remote-path", "/slack",
"--local-layout", "scoped",
"--once",
}, strings.NewReader(""), &bytes.Buffer{}, &bytes.Buffer{})
if err == nil ||
!strings.Contains(err.Error(), "--local-layout=scoped") ||
!strings.Contains(err.Error(), "temporarily unavailable") {
t.Fatalf("expected scoped-layout refusal for repeated remote paths, got %v", err)
}
if got := requests.Load(); got != 0 {
t.Fatalf("scoped multi-path mount contacted server %d times before refusal", got)
}
if _, statErr := os.Stat(localRoot); !errors.Is(statErr, os.ErrNotExist) {
t.Fatalf("scoped multi-path mount initialized mirror before refusal: %v", statErr)
}
}

// skipUntilScopedOperatorSurfacesReady keeps the scoped-runtime coverage
// dormant only while the CLI guard is active. When the guard is removed, the
// helper stops skipping these tests automatically; if the guard is deleted
Expand Down
Loading