File size: 7,138 Bytes
e249c6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import fs from "node:fs/promises";
import path from "node:path";
import { configureFsSafeNative, getFsSafeNativeConfig } from "@openclaw/fs-safe/config";
import { afterEach, assert, beforeEach, describe, expect, it, vi } from "vitest";
import { getApfsCloneId } from "../../../test/helpers/apfs.js";
import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js";
import * as runner from "../../process/exec-runner.js";
import { drainGlobalSingletonLifecycleState } from "../../shared/global-singleton.js";
import { detectWorktreeFilesystemBackend } from "./filesystem-backend.js";
import { nativeWorktreeFilesystem } from "./filesystem-native.js";

describe.skipIf(process.platform !== "darwin")("isolated native worktree operations", () => {
  const tempDirs = useAutoCleanupTempDirTracker(afterEach);
  const options = { commitGuard: () => {} };
  beforeEach(() => {
    for (const name of [
      "FS_SAFE_NATIVE_MODE",
      "OPENCLAW_FS_SAFE_NATIVE_MODE",
      "FS_SAFE_PYTHON_MODE",
      "OPENCLAW_FS_SAFE_PYTHON_MODE",
    ]) {
      vi.stubEnv(name, undefined);
    }
    configureFsSafeNative({ mode: "off" });
  });
  afterEach(async () => {
    await drainGlobalSingletonLifecycleState();
    vi.restoreAllMocks();
    vi.unstubAllEnvs();
  });

  it("clones and reads provenance with the host's native helper still off", async () => {
    const root = tempDirs.make("openclaw-native-policy-");
    const backend = await detectWorktreeFilesystemBackend(root, options);
    assert(backend);
    expect(backend?.id).toBe("apfs");
    const source = path.join(root, "source");
    const destination = path.join(root, "destination");
    await backend.createTemplate(source, options);
    await fs.writeFile(path.join(source, "payload"), Buffer.alloc(1024 * 1024, 0x5a));
    await backend.cloneTemplate(source, destination, options);
    const [original, cloned] = await nativeWorktreeFilesystem.readMetadata(
      [path.join(source, "payload"), path.join(destination, "payload")],
      options,
    );
    expect(original?.cloneId).toBe(getApfsCloneId(path.join(source, "payload")));
    expect(cloned?.cloneId).toBe(original?.cloneId);
    expect(cloned?.ino).not.toBe(original?.ino);
    expect(await fs.readFile(path.join(destination, "payload"))).toEqual(
      await fs.readFile(path.join(source, "payload")),
    );
    expect(getFsSafeNativeConfig().mode).toBe("off");
  });

  it.each(["FS_SAFE_NATIVE_MODE", "OPENCLAW_FS_SAFE_NATIVE_MODE"])(
    "honors explicit %s=off in read and write children",
    async (name) => {
      vi.stubEnv(name, "off");
      const root = tempDirs.make("openclaw-native-disabled-");
      const source = path.join(root, "source");
      const destination = path.join(root, "destination");
      await fs.mkdir(source);
      await fs.writeFile(path.join(source, "payload"), "source");
      expect(await detectWorktreeFilesystemBackend(root, options)).toBeNull();
      await expect(
        nativeWorktreeFilesystem.copy(source, destination, options),
      ).rejects.toMatchObject({
        code: "helper-unavailable",
      });
      await expect(fs.access(destination)).rejects.toMatchObject({ code: "ENOENT" });
      expect(getFsSafeNativeConfig().mode).toBe("off");
    },
  );

  it.each(["copy", "createSource"])(
    "revalidates allocation authority before child %s input",
    async (operation) => {
      const root = tempDirs.make("openclaw-native-admission-");
      const source = path.join(root, "source");
      const destination = path.join(root, "destination");
      await fs.mkdir(source);
      await fs.writeFile(path.join(source, "payload"), "source");
      const run = runner.runCommandBuffersWithTimeout;
      let authorized = true;
      let childPid: number | undefined;
      vi.spyOn(runner, "runCommandBuffersWithTimeout").mockImplementation(
        (argv, commandOptions) => {
          if (typeof commandOptions === "number") {
            throw new Error("Native writes need guarded input");
          }
          return run(argv, {
            ...commandOptions,
            beforeInput(pid) {
              childPid = pid;
              authorized = false;
              commandOptions.beforeInput?.(pid);
            },
          });
        },
      );
      const writeOptions = {
        commitGuard() {
          if (!authorized) {
            throw new Error("allocation lease lost");
          }
        },
      };
      await expect(
        operation === "copy"
          ? nativeWorktreeFilesystem.copy(source, destination, writeOptions)
          : nativeWorktreeFilesystem.createSource(destination, writeOptions),
      ).rejects.toThrow("allocation lease lost");
      expect(childPid).toBeGreaterThan(0);
      await expect(fs.access(destination)).rejects.toMatchObject({ code: "ENOENT" });
    },
  );

  it("joins an admitted write child after cancellation before returning to cleanup", async () => {
    const root = tempDirs.make("openclaw-native-settlement-");
    const ready = path.join(root, "ready");
    const canceled = path.join(root, "canceled");
    const release = path.join(root, "release");
    const finished = path.join(root, "finished");
    const run = runner.runCommandBuffersWithTimeout;
    // A controlled write child makes settlement observable independently of disk speed.
    const script = `
      import fs from 'node:fs';
      import path from 'node:path';
      import { setTimeout } from 'node:timers/promises';
      import { serialize } from 'node:v8';
      const root = process.argv[1];
      process.on('SIGTERM', () => fs.writeFileSync(path.join(root, 'canceled'), 'yes'));
      JSON.parse(fs.readFileSync(0, 'utf8'));
      fs.writeFileSync(path.join(root, 'ready'), 'yes');
      while (!fs.existsSync(path.join(root, 'release'))) await setTimeout(10);
      fs.writeFileSync(path.join(root, 'finished'), 'yes');
      process.stdout.write(serialize({ type: 'written' }));
    `;
    vi.spyOn(runner, "runCommandBuffersWithTimeout").mockImplementation((_argv, commandOptions) =>
      run([process.execPath, "--input-type=module", "--eval", script, root], commandOptions),
    );
    const controller = new AbortController();
    let settled = false;
    const pending = nativeWorktreeFilesystem
      .copy(path.join(root, "source"), path.join(root, "destination"), {
        ...options,
        signal: controller.signal,
      })
      .then(
        () => undefined,
        (error: unknown) => error,
      )
      .finally(() => {
        settled = true;
      });
    try {
      await expect.poll(() => fs.readFile(ready, "utf8").catch(() => "")).toBe("yes");
      controller.abort(new Error("allocation canceled"));
      await expect.poll(() => fs.readFile(canceled, "utf8").catch(() => "")).toBe("yes");
      expect(settled).toBe(false);
      await expect(fs.access(finished)).rejects.toMatchObject({ code: "ENOENT" });
    } finally {
      await fs.writeFile(release, "yes");
      await pending;
    }
    expect(await pending).toMatchObject({ message: "allocation canceled" });
    expect(await fs.readFile(finished, "utf8")).toBe("yes");
  });
});