diff --git a/backend/test/auth-runtime-projection.test.ts b/backend/test/auth-runtime-projection.test.ts
index 5a9a58f2..06809297 100644
--- a/backend/test/auth-runtime-projection.test.ts
+++ b/backend/test/auth-runtime-projection.test.ts
@@ -70,6 +70,7 @@ const passwordHash =
"$argon2id$v=19$m=65536,t=3,p=1$AAECAwQFBgcICQoLDA0ODw$DRo8ZSPI8G5OCvnFFapbVEjP69aDjy1Sw9i2743cPC4";
const userId = "6ba7b810-9dad-4ed1-80b4-00c04fd430c8";
const roots: string[] = [];
+const linuxTest = test.runIf(process.platform === "linux");
afterEach(() => {
fsHook.path = undefined;
@@ -276,7 +277,7 @@ function expectDenied(operation: () => unknown): void {
}
}
-test("loads ready projection as one immutable auth and local-users snapshot", () => {
+linuxTest("loads ready projection as one immutable auth and local-users snapshot", () => {
const root = projectionRoot();
const fixture = localProjectionFixture("synthetic-user", passwordHash);
const generation = writeReadyProjection(root, fixture);
@@ -298,7 +299,7 @@ test("loads ready projection as one immutable auth and local-users snapshot", ()
).toBe(true);
});
-test("loads a complete OIDC projection without a users snapshot", () => {
+linuxTest("loads a complete OIDC projection without a users snapshot", () => {
const root = projectionRoot();
const generation = writeReadyOidcProjection(root);
const loaded = createProjectedAuthenticationConfigProvider(root).current();
@@ -309,7 +310,7 @@ test("loads a complete OIDC projection without a users snapshot", () => {
});
});
-test("loadConfig selects an immutable projected local provider and its in-memory registry", async () => {
+linuxTest("loadConfig selects an immutable projected local provider and its in-memory registry", async () => {
const root = projectionRoot();
writeReadyProjection(root, localProjectionFixture("projected-user", passwordHash));
@@ -323,7 +324,7 @@ test("loadConfig selects an immutable projected local provider and its in-memory
expect(await registry?.findByUsername("PROJECTED-USER")).toMatchObject({ username: "projected-user" });
});
-test("loadConfig selects an immutable projected OIDC provider without direct-file fallback", () => {
+linuxTest("loadConfig selects an immutable projected OIDC provider without direct-file fallback", () => {
const root = projectionRoot();
writeReadyOidcProjection(root);
@@ -338,7 +339,7 @@ test("loadConfig selects an immutable projected OIDC provider without direct-fil
});
});
-test("rejects a trailing-slash runtime root", () => {
+linuxTest("rejects a trailing-slash runtime root", () => {
const root = projectionRoot();
writeReadyProjection(
root,
@@ -349,7 +350,7 @@ test("rejects a trailing-slash runtime root", () => {
);
});
-test.each([
+linuxTest.each([
["missing", undefined],
[
"blocked",
@@ -381,7 +382,7 @@ test.each([
);
});
-test.each([
+linuxTest.each([
"root traversal",
"CURRENT symlink",
"CURRENT hardlink",
@@ -436,7 +437,7 @@ test.runIf(process.geteuid?.() === 0)(
},
);
-test("rejects a foreign group with the correct owner", () => {
+linuxTest("rejects a foreign group with the correct owner", () => {
const root = projectionRoot();
writeReadyProjection(
root,
@@ -449,7 +450,7 @@ test("rejects a foreign group with the correct owner", () => {
);
});
-test("enumerates closed namespaces without path-based readdirSync", () => {
+linuxTest("enumerates closed namespaces without path-based readdirSync", () => {
const root = projectionRoot();
const generation = writeReadyProjection(
root,
@@ -462,7 +463,7 @@ test("enumerates closed namespaces without path-based readdirSync", () => {
).toBe(generation);
});
-test("rejects a symlinked runtime root", () => {
+linuxTest("rejects a symlinked runtime root", () => {
const root = projectionRoot();
writeReadyProjection(
root,
@@ -476,7 +477,7 @@ test("rejects a symlinked runtime root", () => {
);
});
-test.each([
+linuxTest.each([
"root",
"generations",
"selected generation",
@@ -514,7 +515,7 @@ test.each([
);
});
-test.each(["manifest", "generation", "size", "digest"])(
+linuxTest.each(["manifest", "generation", "size", "digest"])(
"rejects changed %s integrity data without secret disclosure",
(kind) => {
const root = projectionRoot();
@@ -542,7 +543,7 @@ test.each(["manifest", "generation", "size", "digest"])(
},
);
-test("switches atomically to a later complete generation", () => {
+linuxTest("switches atomically to a later complete generation", () => {
const root = projectionRoot();
const first = writeReadyProjection(
root,
@@ -564,7 +565,7 @@ test("switches atomically to a later complete generation", () => {
expect(provider.current().runtimeProjection?.generation).toBe(second);
});
-test("retries once when CURRENT is atomically replaced between lstat and open", () => {
+linuxTest("retries once when CURRENT is atomically replaced between lstat and open", () => {
const root = projectionRoot();
const first = writeReadyProjection(
root,
@@ -591,7 +592,7 @@ test("retries once when CURRENT is atomically replaced between lstat and open",
).toBe(second);
});
-test("retries once when CURRENT is replaced after the final identity read", () => {
+linuxTest("retries once when CURRENT is replaced after the final identity read", () => {
const root = projectionRoot();
const first = writeReadyProjection(
root,
@@ -628,7 +629,7 @@ test("retries once when CURRENT is replaced after the final identity read", () =
expect(observations).toBe(3);
});
-test("retries once when CURRENT is replaced between root descriptor and path observations", () => {
+linuxTest("retries once when CURRENT is replaced between root descriptor and path observations", () => {
const root = projectionRoot();
const first = writeReadyProjection(
root,
@@ -678,7 +679,7 @@ test("retries once when CURRENT is replaced between root descriptor and path obs
expect(replacedCurrent).toBe(true);
});
-test("rejects a second CURRENT replacement after the one permitted retry", () => {
+linuxTest("rejects a second CURRENT replacement after the one permitted retry", () => {
const root = projectionRoot();
const first = writeReadyProjection(
root,
@@ -717,7 +718,7 @@ test("rejects a second CURRENT replacement after the one permitted retry", () =>
);
});
-test("fails deterministically when generations is replaced during a load", () => {
+linuxTest("fails deterministically when generations is replaced during a load", () => {
const root = projectionRoot();
const generation = writeReadyProjection(
root,
@@ -748,7 +749,7 @@ test("fails deterministically when generations is replaced during a load", () =>
);
});
-test("fails deterministically when the selected generation directory is replaced during a load", () => {
+linuxTest("fails deterministically when the selected generation directory is replaced during a load", () => {
const root = projectionRoot();
const generation = writeReadyProjection(
root,
@@ -780,7 +781,7 @@ test("fails deterministically when the selected generation directory is replaced
);
});
-test.each(["corrupt", "symlink"])(
+linuxTest.each(["corrupt", "symlink"])(
"rejects a %s retained predecessor generation",
(kind) => {
const root = projectionRoot();
@@ -819,7 +820,7 @@ test.each(["corrupt", "symlink"])(
},
);
-test("has no direct-file fallback when CURRENT is absent", () => {
+linuxTest("has no direct-file fallback when CURRENT is absent", () => {
const root = projectionRoot();
const generation = writeReadyProjection(
root,
@@ -839,7 +840,7 @@ test("has no direct-file fallback when CURRENT is absent", () => {
);
});
-test("in-flight snapshot authenticates A after selection B and deletion A, while a new load sees B", async () => {
+linuxTest("in-flight snapshot authenticates A after selection B and deletion A, while a new load sees B", async () => {
const root = projectionRoot();
const first = writeReadyProjection(
root,
diff --git a/backend/test/pi-management.test.ts b/backend/test/pi-management.test.ts
index 452deae9..f0d79e8d 100644
--- a/backend/test/pi-management.test.ts
+++ b/backend/test/pi-management.test.ts
@@ -198,7 +198,10 @@ test("smoke uses the configured timeout and reports a sanitized timeout", async
message: "Pi smoke check timed out",
checkedAt: "2026-08-05T10:00:00.000Z",
});
- expect(calls).toEqual([{ command: "/usr/local/bin/pi", args: ["--version"], timeout: 750 }]);
+ expect(calls).toHaveLength(1);
+ expect(calls[0]).toMatchObject({ command: "/usr/local/bin/pi", args: ["--version"] });
+ expect(calls[0]!.timeout).toBeGreaterThan(0);
+ expect(calls[0]!.timeout).toBeLessThanOrEqual(750);
});
// Catches a smoke endpoint that validates only the Pi binary/model catalogue and never makes a
diff --git a/frontend/src/shell/AppShell.auth.test.tsx b/frontend/src/shell/AppShell.auth.test.tsx
index 7617fd66..7146b812 100644
--- a/frontend/src/shell/AppShell.auth.test.tsx
+++ b/frontend/src/shell/AppShell.auth.test.tsx
@@ -65,7 +65,7 @@ describe("authenticated shell permissions", () => {
expect(screen.getByRole("button", { name: "All sessions" })).toBeInTheDocument();
});
- test("hides identity and logout outside local authentication", async () => {
+ test("keeps identity but hides logout outside local authentication", async () => {
let logoutCalls = 0;
server.use(http.post("/api/auth/logout", () => {
logoutCalls += 1;
@@ -79,7 +79,7 @@ describe("authenticated shell permissions", () => {
permissions: ["session.use"],
}, false);
- await waitFor(() => expect(screen.queryByText("portal-user")).not.toBeInTheDocument());
+ expect(await screen.findByText("portal-user")).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Log out" })).not.toBeInTheDocument();
expect(logoutCalls).toBe(0);
});
diff --git a/frontend/src/shell/AppShell.tsx b/frontend/src/shell/AppShell.tsx
index 388ee9e3..d7097cd8 100644
--- a/frontend/src/shell/AppShell.tsx
+++ b/frontend/src/shell/AppShell.tsx
@@ -716,14 +716,16 @@ export function AppShell({ canLogout }: AppShellProps) {
Human In The Loop