920 lines · 40.0 KB · TypeScript
RawDownload
import * as NodeServices from "@effect/platform-node/NodeServices";import { expect, it } from "@effect/vitest";import {  HostProcessArchitecture,  HostProcessEnvironment,  HostProcessPlatform,} from "@t3tools/shared/hostProcess";import * as Deferred from "effect/Deferred";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Fiber from "effect/Fiber";import * as FileSystem from "effect/FileSystem";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as PlatformError from "effect/PlatformError";import * as Queue from "effect/Queue";import * as Schema from "effect/Schema";import * as Scope from "effect/Scope";import * as Sink from "effect/Sink";import * as Stream from "effect/Stream";import { HttpClient, HttpClientResponse } from "effect/unstable/http";import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";import * as NodeCrypto from "node:crypto"; import {  makeAntigravityInstallation,  type AntigravityExecutable,  type AntigravityInstallation,  type AntigravityInstallationOptions,} from "./AntigravityInstallation.ts";import { ANTIGRAVITY_AUTH_BROWSER_MARKER } from "./antigravityAuthSupport.ts";import type { AntigravityReleaseAsset } from "./antigravityRelease.ts"; const serverContents = "antigravity runtime\n";const harnessContents = "local harness\n";const previousReleaseId = "1".repeat(64);const previousVersion = "fixture-old";const encodeJsonString = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)); // Small ZIPs made with Python's zipfile module. The unsafe entries are intentional.const zipFixtures = {  complete:    "UEsDBBQAAAAIAAAAIl1zEy/oFAAAABQAAAASAAAAYWd5X2FjcF9zZXJ2ZXIucGFyS8wryUwvSizLLKlUKCoFcnJTuQBQSwMEFAAAAAgAAAAiXV9yAykQAAAADgAAABUAAABsb2NhbGhhcm5lc3NfZXh0ZXJuYWzLyU9OzFHISCzKSy0u5gIAUEsBAhQDFAAAAAgAAAAiXXMTL+gUAAAAFAAAABIAAAAAAAAAAAAAAO2BAAAAAGFneV9hY3Bfc2VydmVyLnBhclBLAQIUAxQAAAAIAAAAIl1fcgMpEAAAAA4AAAAVAAAAAAAAAAAAAADtgUQAAABsb2NhbGhhcm5lc3NfZXh0ZXJuYWxQSwUGAAAAAAIAAgCDAAAAhwAAAAAA",  missingHarness:    "UEsDBBQAAAAIAAAAIl1zEy/oFAAAABQAAAASAAAAYWd5X2FjcF9zZXJ2ZXIucGFyS8wryUwvSizLLKlUKCoFcnJTuQBQSwECFAMUAAAACAAAACJdcxMv6BQAAAAUAAAAEgAAAAAAAAAAAAAA7YEAAAAAYWd5X2FjcF9zZXJ2ZXIucGFyUEsFBgAAAAABAAEAQAAAAEQAAAAAAA==",  duplicate:    "UEsDBBQAAAAIAAAAIl1zEy/oFAAAABQAAAASAAAAYWd5X2FjcF9zZXJ2ZXIucGFyS8wryUwvSizLLKlUKCoFcnJTuQBQSwMEFAAAAAgAAAAiXXMTL+gUAAAAFAAAABIAAABhZ3lfYWNwX3NlcnZlci5wYXJLzCvJTC9KLMssqVQoKgVyclO5AFBLAQIUAxQAAAAIAAAAIl1zEy/oFAAAABQAAAASAAAAAAAAAAAAAADtgQAAAABhZ3lfYWNwX3NlcnZlci5wYXJQSwECFAMUAAAACAAAACJdcxMv6BQAAAAUAAAAEgAAAAAAAAAAAAAA7YFEAAAAYWd5X2FjcF9zZXJ2ZXIucGFyUEsFBgAAAAACAAIAgAAAAIgAAAAAAA==",  traversal:    "UEsDBBQAAAAIAAAAIl1zEy/oFAAAABQAAAAVAAAALi5cYWd5X2FjcF9zZXJ2ZXIucGFyS8wryUwvSizLLKlUKCoFcnJTuQBQSwMEFAAAAAgAAAAiXV9yAykQAAAADgAAABUAAABsb2NhbGhhcm5lc3NfZXh0ZXJuYWzLyU9OzFHISCzKSy0u5gIAUEsBAhQDFAAAAAgAAAAiXXMTL+gUAAAAFAAAABUAAAAAAAAAAAAAAO2BAAAAAC4uXGFneV9hY3Bfc2VydmVyLnBhclBLAQIUAxQAAAAIAAAAIl1fcgMpEAAAAA4AAAAVAAAAAAAAAAAAAADtgUcAAABsb2NhbGhhcm5lc3NfZXh0ZXJuYWxQSwUGAAAAAAIAAgCGAAAAigAAAAAA",  symlink:    "UEsDBBQAAAAIAAAAIl1zEy/oFAAAABQAAAASAAAAYWd5X2FjcF9zZXJ2ZXIucGFyS8wryUwvSizLLKlUKCoFcnJTuQBQSwMEFAAAAAgAAAAiXV9yAykQAAAADgAAABUAAABsb2NhbGhhcm5lc3NfZXh0ZXJuYWzLyU9OzFHISCzKSy0u5gIAUEsBAhQDFAAAAAgAAAAiXXMTL+gUAAAAFAAAABIAAAAAAAAAAAAAAO2BAAAAAGFneV9hY3Bfc2VydmVyLnBhclBLAQIUAxQAAAAIAAAAIl1fcgMpEAAAAA4AAAAVAAAAAAAAAAAAAAD/oUQAAABsb2NhbGhhcm5lc3NfZXh0ZXJuYWxQSwUGAAAAAAIAAgCDAAAAhwAAAAAA",  oversizedMember:    "UEsDBBQAAAAIAAAAIl0WGThFFQAAABUAAAASAAAAYWd5X2FjcF9zZXJ2ZXIucGFyS8wryUwvSizLLKlUKCoFcnJTuSoAUEsDBBQAAAAIAAAAIl1fcgMpEAAAAA4AAAAVAAAAbG9jYWxoYXJuZXNzX2V4dGVybmFsy8lPTsxRyEgsykstLuYCAFBLAQIUAxQAAAAIAAAAIl0WGThFFQAAABUAAAASAAAAAAAAAAAAAADtgQAAAABhZ3lfYWNwX3NlcnZlci5wYXJQSwECFAMUAAAACAAAACJdX3IDKRAAAAAOAAAAFQAAAAAAAAAAAAAA7YFFAAAAbG9jYWxoYXJuZXNzX2V4dGVybmFsUEsFBgAAAAACAAIAgwAAAIgAAAAAAA==",  windows:    "UEsDBBQAAAAIAAAAIl1zEy/oFAAAABQAAAASAAAAYWd5X2FjcF9zZXJ2ZXIuZXhlS8wryUwvSizLLKlUKCoFcnJTuQBQSwMEFAAAAAgAAAAiXV9yAykQAAAADgAAABkAAABsb2NhbGhhcm5lc3NfZXh0ZXJuYWwuZXhly8lPTsxRyEgsykstLuYCAFBLAQIUAxQAAAAIAAAAIl1zEy/oFAAAABQAAAASAAAAAAAAAAAAAADtgQAAAABhZ3lfYWNwX3NlcnZlci5leGVQSwECFAMUAAAACAAAACJdX3IDKRAAAAAOAAAAGQAAAAAAAAAAAAAA7YFEAAAAbG9jYWxoYXJuZXNzX2V4dGVybmFsLmV4ZVBLBQYAAAAAAgACAIcAAACLAAAAAAA=",}; // The installation checks POSIX exec bits off the real filesystem unless the// platform is win32, so a linux platform mock cannot pass on NTFS. Default to// the host and let the fixture names follow; the suite is about install// mechanics, which are the same on every platform.const hostPlatform: NodeJS.Platform =  HostProcessPlatform.defaultValue() === "win32" ? "win32" : "linux";const completeArchive = Buffer.from(  hostPlatform === "win32" ? zipFixtures.windows : zipFixtures.complete,  "base64",);const executableName = hostPlatform === "win32" ? "agy_acp_server.exe" : "agy_acp_server.par";const harnessName =  hostPlatform === "win32" ? "localharness_external.exe" : "localharness_external"; function releaseAsset(  archive: Uint8Array = completeArchive,  platform: NodeJS.Platform = hostPlatform,) {  return {    version: "fixture-new",    url: "https://dl.google.com/antigravity-test.zip",    sha256: NodeCrypto.createHash("sha256").update(archive).digest("hex"),    archiveBytes: archive.byteLength,    executable: {      name: platform === "win32" ? "agy_acp_server.exe" : "agy_acp_server.par",      bytes: Buffer.byteLength(serverContents),    },    harness: {      name: platform === "win32" ? "localharness_external.exe" : "localharness_external",      bytes: Buffer.byteLength(harnessContents),    },  } satisfies AntigravityReleaseAsset;} const writeRelease = Effect.fn("test.writeAntigravityRelease")(function* (  managedDirectory: string,  asset: AntigravityReleaseAsset,  active = true,) {  const fs = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  const directory = path.join(managedDirectory, "versions", asset.sha256);  yield* fs.makeDirectory(directory, { recursive: true });  yield* fs.writeFileString(path.join(directory, asset.executable.name), serverContents, {    mode: 0o755,  });  yield* fs.writeFileString(path.join(directory, asset.harness.name), harnessContents, {    mode: 0o755,  });  yield* fs.writeFileString(    path.join(directory, ".install-complete.json"),    encodeJsonString({      releaseId: asset.sha256,      version: asset.version,      executable: asset.executable,      harness: asset.harness,    }),  );  if (active) {    yield* fs.writeFileString(      path.join(managedDirectory, "active.json"),      encodeJsonString({ releaseId: asset.sha256 }),    );  }}); interface HarnessOptions {  readonly baseDir?: string;  readonly asset?: AntigravityReleaseAsset | null;  readonly archive?: Buffer;  readonly body?: Stream.Stream<Uint8Array> | undefined;  readonly contentLength?: number;  readonly contentEncoding?: string;  readonly platform?: NodeJS.Platform;  readonly path?: string;  readonly previous?: boolean;  readonly fileSystem?: FileSystem.FileSystem;  readonly validate?: AntigravityInstallationOptions["validate"];  readonly useDefaultValidation?: boolean;} const makeHarness = Effect.fn("test.makeAntigravityInstallation")(function* (  options: HarnessOptions = {},) {  const fs = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  const baseDir =    options.baseDir ?? (yield* fs.makeTempDirectoryScoped({ prefix: "t3-agy-test-" }));  const platform = options.platform ?? hostPlatform;  const archive = options.archive ?? completeArchive;  const asset = options.asset === undefined ? releaseAsset(archive, platform) : options.asset;  const managedDirectory = path.join(baseDir, "tools", "antigravity-acp", `${platform}-x64`);  if (options.previous) {    yield* writeRelease(managedDirectory, {      ...releaseAsset(archive, platform),      sha256: previousReleaseId,      version: previousVersion,    });  }  const stagingReleased = yield* Deferred.make<void>();  const requests: string[] = [];  const validations: Array<{ executable: AntigravityExecutable; version: string }> = [];  const installationFs = options.fileSystem ?? fs;  const trackedFs = FileSystem.FileSystem.of({    ...installationFs,    makeTempDirectoryScoped: (settings) =>      settings?.prefix === ".install-"        ? Effect.acquireRelease(installationFs.makeTempDirectory(settings), (directory) =>            fs              .remove(directory, { recursive: true, force: true })              .pipe(Effect.orDie, Effect.andThen(Deferred.succeed(stagingReleased, undefined))),          )        : installationFs.makeTempDirectoryScoped(settings),  });  const installation = yield* makeAntigravityInstallation({    baseDir,    releaseAsset: asset,    ...(options.useDefaultValidation      ? {}      : {          validate: (executable: AntigravityExecutable, version: string) =>            Effect.sync(() => validations.push({ executable, version })).pipe(              Effect.andThen(options.validate?.(executable, version) ?? Effect.void),            ),        }),  }).pipe(    Effect.provideService(FileSystem.FileSystem, trackedFs),    Effect.provideService(HostProcessPlatform, platform),    Effect.provideService(HostProcessArchitecture, "x64"),    Effect.provideService(HostProcessEnvironment, { PATH: options.path ?? "" }),    Effect.provideService(      HttpClient.HttpClient,      HttpClient.make((request) =>        Effect.sync(() => {          requests.push(request.url);          const response = HttpClientResponse.fromWeb(            request,            new Response(null, {              headers: {                ...(options.contentLength === undefined                  ? {}                  : { "content-length": String(options.contentLength) }),                ...(options.contentEncoding === undefined                  ? {}                  : { "content-encoding": options.contentEncoding }),              },            }),          );          return Object.defineProperty(response, "stream", {            value:              options.body ??              Stream.make(                archive.subarray(0, 31),                archive.subarray(31, 149),                archive.subarray(149),              ),          });        }),      ),    ),  );  return { installation, fs, path, baseDir, requests, validations, stagingReleased };}); const terminalState = (installation: AntigravityInstallation["Service"]) =>  installation.changes.pipe(    Stream.filter((state) => ["succeeded", "failed", "cancelled"].includes(state.phase)),    Stream.runHead,    Effect.map(Option.getOrThrow),  ); const expectPreviousRelease = Effect.fn("test.expectPreviousAntigravityRelease")(function* (  installation: AntigravityInstallation["Service"],) {  const fs = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  const resolved = yield* installation.resolve();  expect(resolved.version).toBe(previousVersion);  expect(resolved.managedVersionDirectory).toBe(    path.join(installation.managedDirectory, "versions", previousReleaseId),  );  expect(yield* fs.readFileString(resolved.executablePath)).toBe(serverContents);  expect(yield* fs.readFileString(resolved.harnessPath)).toBe(harnessContents);  expect((yield* installation.state).installedVersion).toBe(previousVersion);}); it.layer(NodeServices.layer)("Antigravity installation", (it) => {  it.effect("verifies both files before activating a streamed download", () =>    Effect.gen(function* () {      const enteredValidation = yield* Deferred.make<void>();      const finishValidation = yield* Deferred.make<void>();      const { installation, fs, path, validations, requests, stagingReleased } = yield* makeHarness(        {          previous: true,          validate: () =>            Deferred.succeed(enteredValidation, undefined).pipe(              Effect.andThen(Deferred.await(finishValidation)),            ),        },      );      const initial = yield* installation.changes.pipe(        Stream.runHead,        Effect.map(Option.getOrThrow),      );      expect(initial).toMatchObject({ phase: "idle", installedVersion: previousVersion });      const started = yield* installation.start;      expect(started).toMatchObject({ phase: "downloading", downloadedBytes: 0 });      yield* Deferred.await(enteredValidation);      yield* expectPreviousRelease(installation);      const validation = validations[0];      expect(validation?.version).toBe("fixture-new");      if (!validation) return yield* Effect.die("Expected runtime validation.");      expect(yield* fs.readFileString(validation.executable.executablePath)).toBe(serverContents);      expect(yield* fs.readFileString(validation.executable.harnessPath)).toBe(harnessContents);       yield* Deferred.succeed(finishValidation, undefined);      expect(yield* terminalState(installation)).toMatchObject({        phase: "succeeded",        operationId: started.operationId,        downloadedBytes: completeArchive.byteLength,        installedVersion: "fixture-new",      });      yield* Deferred.await(stagingReleased);      const selected = yield* installation.resolve();      expect(selected).toMatchObject({ source: "managed", version: "fixture-new" });      expect(path.dirname(selected.harnessPath)).toBe(path.dirname(selected.executablePath));      expect(yield* fs.readFileString(selected.executablePath)).toBe(serverContents);      expect(yield* fs.readFileString(selected.harnessPath)).toBe(harnessContents);      expect(yield* fs.readDirectory(path.join(installation.managedDirectory, "versions"))).toEqual(        expect.arrayContaining([previousReleaseId, releaseAsset().sha256]),      );      expect(requests).toEqual([releaseAsset().url]);    }),  );   it.effect.each([    {      name: "the expected release",      agentName: "antigravity-acp",      version: "fixture-new",      valid: true,    },    { name: "a different agent", agentName: "other-agent", version: "fixture-new", valid: false },    {      name: "a different version",      agentName: "antigravity-acp",      version: "other-version",      valid: false,    },  ])("validates $name with initialize only and removes the disposable profile", (testCase) =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const encoder = new TextEncoder();      const decodeRequest = Schema.decodeUnknownEffect(        Schema.fromJsonString(          Schema.Struct({            id: Schema.Union([Schema.String, Schema.Number]),            method: Schema.String,          }),        ),      );      const methods: string[] = [];      const profiles = new Set<string>();      let closedRuntimes = 0;      const spawner = ChildProcessSpawner.make(        Effect.fn("test.spawnAntigravityValidator")(function* (command) {          if (command._tag !== "StandardCommand") {            return yield* Effect.die("Expected one validation process.");          }          const profile = command.options.env?.GEMINI_HOME;          if (!profile) return yield* Effect.die("Expected a disposable validation profile.");          profiles.add(profile);          const helper = command.args[0] === "-e";          const output = yield* Queue.unbounded<Uint8Array>();          const exited = yield* Deferred.make<ChildProcessSpawner.ExitCode>();          const terminate = Deferred.succeed(exited, ChildProcessSpawner.ExitCode(0)).pipe(            Effect.asVoid,          );          yield* Effect.addFinalizer(() =>            terminate.pipe(              Effect.andThen(Queue.shutdown(output)),              Effect.andThen(                Effect.sync(() => {                  if (!helper) closedRuntimes += 1;                }),              ),            ),          );          return ChildProcessSpawner.makeHandle({            pid: ChildProcessSpawner.ProcessId(helper ? 1 : 2),            exitCode: helper              ? Effect.succeed(ChildProcessSpawner.ExitCode(0))              : Deferred.await(exited),            isRunning: Deferred.isDone(exited).pipe(Effect.map((done) => !done)),            kill: () => terminate,            unref: Effect.succeed(Effect.void),            stdin: Sink.forEach((bytes: Uint8Array) =>              Effect.gen(function* () {                const request = yield* decodeRequest(new TextDecoder().decode(bytes)).pipe(                  Effect.orDie,                );                methods.push(request.method);                yield* Queue.offer(                  output,                  encoder.encode(                    `${encodeJsonString({                      jsonrpc: "2.0",                      id: request.id,                      ...(request.method === "initialize"                        ? {                            result: {                              protocolVersion: 1,                              agentInfo: { name: testCase.agentName, version: testCase.version },                              agentCapabilities: {                                loadSession: true,                                sessionCapabilities: { resume: {} },                                auth: { logout: {} },                              },                              authMethods: [{ id: "oauth-personal", name: "Google" }],                            },                          }                        : {                            error: {                              code: -32601,                              message: "Validation must not sign in or create sessions.",                            },                          }),                    })}\n`,                  ),                );              }),            ),            stdout: helper ? Stream.empty : Stream.fromQueue(output),            stderr: helper              ? Stream.make(                  encoder.encode(                    `${ANTIGRAVITY_AUTH_BROWSER_MARKER}${encodeJsonString(command.args.at(-1))}\n`,                  ),                )              : Stream.empty,            all: Stream.empty,            getInputFd: () => Sink.drain,            getOutputFd: () => Stream.empty,          });        }),      );      const { installation, stagingReleased } = yield* makeHarness({        previous: true,        useDefaultValidation: true,      }).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner));      yield* installation.start;      expect((yield* terminalState(installation)).phase).toBe(        testCase.valid ? "succeeded" : "failed",      );      yield* Deferred.await(stagingReleased);      expect(methods).toEqual(["initialize"]);      expect(closedRuntimes).toBe(1);      expect(profiles.size).toBe(1);      for (const profile of profiles) {        expect(yield* fs.exists(profile)).toBe(false);      }      if (testCase.valid) {        expect((yield* installation.resolve()).version).toBe("fixture-new");      } else {        yield* expectPreviousRelease(installation);      }    }),  );   it.effect("accepts an encoded Content-Length when the body is compressed", () =>    Effect.gen(function* () {      // dl.google.com gzips the archive and reports the encoded size. The decoded      // stream is still checked byte for byte and by hash.      const { installation, validations } = yield* makeHarness({        contentLength: completeArchive.byteLength - 1_000,        contentEncoding: "gzip",      });      yield* installation.start;      const state = yield* terminalState(installation);      expect(state.phase).toBe("succeeded");      expect(validations).toHaveLength(1);    }),  );   it.effect.each([    { name: "checksum mismatch", asset: { ...releaseAsset(), sha256: "2".repeat(64) } },    { name: "short download", archive: completeArchive.subarray(0, -1), asset: releaseAsset() },    {      name: "oversized download",      archive: Buffer.concat([completeArchive, Buffer.from("extra")]),      asset: releaseAsset(),    },    { name: "wrong Content-Length", contentLength: completeArchive.byteLength + 1 },    { name: "missing harness", archive: Buffer.from(zipFixtures.missingHarness, "base64") },    { name: "duplicate executable", archive: Buffer.from(zipFixtures.duplicate, "base64") },    { name: "path traversal", archive: Buffer.from(zipFixtures.traversal, "base64") },    { name: "symbolic link", archive: Buffer.from(zipFixtures.symlink, "base64") },    { name: "oversized member", archive: Buffer.from(zipFixtures.oversizedMember, "base64") },  ])("rejects $name before runtime validation", (options) =>    Effect.gen(function* () {      const { installation, validations, stagingReleased, fs, path } = yield* makeHarness({        ...options,        previous: true,      });      yield* installation.start;      const state = yield* terminalState(installation);      expect(state.phase).toBe("failed");      expect(state.message).toBeTruthy();      expect(validations).toEqual([]);      yield* Deferred.await(stagingReleased);      yield* expectPreviousRelease(installation);      expect(yield* fs.readDirectory(path.join(installation.managedDirectory, "versions"))).toEqual(        [previousReleaseId],      );    }),  );   it.effect.each(["download", "extract", "active pointer"] as const)(    "preserves the old runtime after an ENOSPC error during %s",    (stage) =>      Effect.gen(function* () {        const fs = yield* FileSystem.FileSystem;        const noSpace = PlatformError.systemError({          _tag: "Unknown",          module: "FileSystem",          method: "write",          description: "ENOSPC: no space left on device",        });        const fileSystem = FileSystem.FileSystem.of({          ...fs,          sink: (target, options) =>            (stage === "download" && target.endsWith("download.zip")) ||            (stage === "extract" && target.endsWith(executableName))              ? fs.sink(target, options).pipe(Sink.mapInputEffect(() => Effect.fail(noSpace)))              : fs.sink(target, options),          writeFileString: (target, content, options) =>            stage === "active pointer" && target.endsWith("contents.tmp")              ? Effect.fail(noSpace)              : fs.writeFileString(target, content, options),        });        const { installation, stagingReleased, path } = yield* makeHarness({          previous: true,          fileSystem,

Lines 1–500 of 920.