@botlearn-course/daemon 0.0.20-beta.7 → 0.0.20-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,12 +3,18 @@ export declare const WORKSPACE_IMAGE_LOGICAL_BYTES: number;
3
3
  interface MountInfoEntry {
4
4
  target: string;
5
5
  filesystemType: string;
6
+ source: string;
6
7
  mountOptions: Set<string>;
8
+ superOptions: Set<string>;
9
+ }
10
+ export interface WorkspaceCapacityProbes {
11
+ statfsBytes(workspaceRoot: string): bigint;
12
+ backingFileBytes(imagePath: string): bigint;
7
13
  }
8
14
  export declare function parseWorkspaceMountInfo(text: string): MountInfoEntry[];
9
15
  export declare function nasWorkspaceEnabled(env?: NodeJS.ProcessEnv): boolean;
10
16
  export declare function workspaceFilesystemFlushCommand(env?: NodeJS.ProcessEnv): [string, string[]] | null;
11
17
  export declare function flushWorkspaceFilesystem(env?: NodeJS.ProcessEnv): Promise<void>;
12
18
  export declare function isWorkspaceQuotaExceededError(error: unknown): boolean;
13
- export declare function assertWorkspaceFilesystem(workspaceRoot: string, env?: NodeJS.ProcessEnv, mountInfo?: string): void;
19
+ export declare function assertWorkspaceFilesystem(workspaceRoot: string, env?: NodeJS.ProcessEnv, mountInfo?: string, capacityProbes?: WorkspaceCapacityProbes): void;
14
20
  export {};
@@ -1,11 +1,20 @@
1
1
  import { execFile } from "node:child_process";
2
- import { readFileSync, statfsSync } from "node:fs";
2
+ import { readFileSync, statfsSync, statSync } from "node:fs";
3
3
  import { promisify } from "node:util";
4
4
  import { assertRuntimeWorkspaceQuota } from "./workspace-quota.js";
5
5
  export const WORKSPACE_IMAGE_FORMAT = "botlearn-workspace-image/1";
6
6
  export const WORKSPACE_IMAGE_LOGICAL_BYTES = 3 * 1024 * 1024 * 1024;
7
7
  const RUNTIME_LAUNCHER = "/opt/botlearn/bin/botlearn-runtime-launcher";
8
8
  const execFileAsync = promisify(execFile);
9
+ const defaultCapacityProbes = {
10
+ statfsBytes(workspaceRoot) {
11
+ const filesystem = statfsSync(workspaceRoot, { bigint: true });
12
+ return filesystem.blocks * filesystem.bsize;
13
+ },
14
+ backingFileBytes(imagePath) {
15
+ return BigInt(statSync(imagePath).size);
16
+ },
17
+ };
9
18
  function decodeMountInfoPath(value) {
10
19
  return value.replace(/\\([0-7]{3})/gu, (_match, octal) => String.fromCharCode(Number.parseInt(octal, 8)));
11
20
  }
@@ -20,7 +29,9 @@ export function parseWorkspaceMountInfo(text) {
20
29
  return {
21
30
  target: decodeMountInfoPath(fields[4]),
22
31
  filesystemType: filesystem[0],
32
+ source: decodeMountInfoPath(filesystem[1]),
23
33
  mountOptions: new Set(fields[5].split(",")),
34
+ superOptions: new Set(filesystem[2].split(",")),
24
35
  };
25
36
  });
26
37
  }
@@ -70,7 +81,7 @@ export function isWorkspaceQuotaExceededError(error) {
70
81
  || /\b(?:ENOSPC|EDQUOT|no space left on device|disk quota exceeded)\b/iu.test(detail)
71
82
  || (candidate.cause !== undefined && isWorkspaceQuotaExceededError(candidate.cause));
72
83
  }
73
- export function assertWorkspaceFilesystem(workspaceRoot, env = process.env, mountInfo) {
84
+ export function assertWorkspaceFilesystem(workspaceRoot, env = process.env, mountInfo, capacityProbes) {
74
85
  if (!nasWorkspaceEnabled(env)) {
75
86
  assertRuntimeWorkspaceQuota(workspaceRoot);
76
87
  return;
@@ -84,17 +95,26 @@ export function assertWorkspaceFilesystem(workspaceRoot, env = process.env, moun
84
95
  return;
85
96
  const mount = parseWorkspaceMountInfo(mountInfo ?? readFileSync("/proc/self/mountinfo", "utf8"))
86
97
  .find((candidate) => candidate.target === workspaceRoot);
98
+ const validFilesystem = mount?.filesystemType === "ext4" || (mount?.filesystemType === "fuse.ext4"
99
+ && mount.source === "/mnt/botlearn-workspace-store/workspace.ext4"
100
+ && mount.superOptions.has("allow_other"));
87
101
  if (!mount
88
- || mount.filesystemType !== "ext4"
102
+ || !validFilesystem
89
103
  || !mount.mountOptions.has("rw")
90
104
  || !mount.mountOptions.has("nodev")
91
105
  || !mount.mountOptions.has("nosuid")) {
92
106
  throw new Error("workspace_loop_mount_failed");
93
107
  }
94
- if (env.NODE_ENV === "test")
108
+ if (env.NODE_ENV === "test" && capacityProbes === undefined)
95
109
  return;
96
- const filesystem = statfsSync(workspaceRoot, { bigint: true });
97
- if (filesystem.blocks * filesystem.bsize !== BigInt(WORKSPACE_IMAGE_LOGICAL_BYTES)) {
110
+ const probes = capacityProbes ?? defaultCapacityProbes;
111
+ // fuse2fs reports a zero-sized statfs view in veFaaS even though the exact backing
112
+ // image is mounted and enforces the filesystem boundary. The mount proof above pins
113
+ // that image, so its validated file size is the authoritative capacity for FUSE.
114
+ const boundedBytes = mount.filesystemType === "fuse.ext4"
115
+ ? probes.backingFileBytes(mount.source)
116
+ : probes.statfsBytes(workspaceRoot);
117
+ if (boundedBytes !== BigInt(WORKSPACE_IMAGE_LOGICAL_BYTES)) {
98
118
  throw new Error("workspace_quota_mismatch");
99
119
  }
100
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botlearn-course/daemon",
3
- "version": "0.0.20-beta.7",
3
+ "version": "0.0.20-beta.9",
4
4
  "description": "Lightweight BotLearn Course daemon: run course tasks on your own machine with your own agent runtime (BYOA).",
5
5
  "type": "module",
6
6
  "bin": {