feat: componentize GStack 2 runtime and release integrity

This commit is contained in:
Sinabina
2026-07-20 14:16:23 -07:00
parent b0ea2296d1
commit f14445bb00
270 changed files with 9681 additions and 51572 deletions
@@ -10,6 +10,10 @@
import SwiftUI
#if canImport(UIKit)
import UIKit
#endif
#if DEBUG
import DebugBridgeCore
#endif
@@ -48,13 +52,47 @@ struct ContentView: View {
Text("StateServer should be on :9999")
.font(.subheadline)
.foregroundColor(.secondary)
Button("Tap (\(counter))") {
counter += 1
}
.buttonStyle(.borderedProminent)
.accessibilityIdentifier("tap-button")
#if canImport(UIKit)
FixtureButton(counter: $counter)
.frame(minWidth: 120, minHeight: 44)
#else
Button("Tap (\(counter))") { counter += 1 }
.accessibilityIdentifier("tap-button")
#endif
}
.padding()
.accessibilityIdentifier("fixture-content")
}
}
#if canImport(UIKit)
/// A real UIKit control inside the SwiftUI fixture. The DebugBridge scanner is
/// intentionally in-process (not XCTest's private accessibility daemon), so a
/// UIViewRepresentable gives the physical-device lane a public, enumerable
/// accessibility element while still exercising SwiftUI state updates.
struct FixtureButton: UIViewRepresentable {
@Binding var counter: Int
func makeCoordinator() -> Coordinator { Coordinator(self) }
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .system)
button.configuration = .borderedProminent()
button.accessibilityIdentifier = "tap-button"
button.addTarget(context.coordinator, action: #selector(Coordinator.tap), for: .touchUpInside)
return button
}
func updateUIView(_ button: UIButton, context: Context) {
context.coordinator.parent = self
button.setTitle("Tap (\(counter))", for: .normal)
button.accessibilityLabel = "Tap (\(counter))"
}
final class Coordinator: NSObject {
var parent: FixtureButton
init(_ parent: FixtureButton) { self.parent = parent }
@objc func tap() { parent.counter += 1 }
}
}
#endif
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { hostname } from "node:os";
import {
acquireLock,
cleanupRuntime,
@@ -33,6 +34,22 @@ afterEach(async () => {
});
describe("runtime cleanup boundary", () => {
test("immediately reclaims a same-host lock whose owner PID is dead", async () => {
const home = await temporaryHome();
const lockPath = runtimeLifecycleLockPath(home);
await fs.mkdir(lockPath, { mode: 0o700 });
await fs.writeFile(path.join(lockPath, "owner.json"), JSON.stringify({
token: "dead-owner",
pid: 2_147_483_647,
hostname: hostname(),
createdAt: new Date().toISOString(),
}));
const started = Date.now();
const release = await acquireLock(lockPath, { staleMs: 60 * 60 * 1000, timeoutMs: 1_000 });
expect(Date.now() - started).toBeLessThan(1_000);
await release();
});
test("retries transient Windows lock creation and rename races", async () => {
const home = await temporaryHome();
const lockPath = path.join(home, "locks", "windows-race.lock");
+56 -1
View File
@@ -46,7 +46,7 @@ describe("Context.dev privacy and failure contract", () => {
const home = path.join(root, "state");
const stream = { write: (_value: string) => {} };
try {
expect(await main(["context", "setup", "--consent"], {
expect(await main(["context", "setup", "--consent", "--offline"], {
env: { GSTACK_HOME: home, CONTEXT_DEV_API_KEY: "future-format-12345" },
cwd: root,
stdout: stream,
@@ -54,12 +54,67 @@ describe("Context.dev privacy and failure contract", () => {
})).toBe(0);
expect(await loadConfig(home)).toMatchObject({
network: { mode: "context", consent: true, selection: "context" },
context: { validation: { status: "unverified", checkedAt: null } },
});
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("Context setup becomes ready only after provider validation succeeds", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-context-verified-"));
const home = path.join(root, "state");
const stream = { write: (_value: string) => {} };
let validated = 0;
try {
expect(await main(["context", "setup", "--consent"], {
env: { GSTACK_HOME: home, CONTEXT_DEV_API_KEY: "future-format-12345" },
cwd: root,
stdout: stream,
stderr: stream,
contextClientFactory: () => ({
scrapeMarkdown: async () => { validated += 1; return { success: true }; },
}),
})).toBe(0);
expect(validated).toBe(1);
expect(await loadConfig(home)).toMatchObject({
network: { mode: "context", consent: true, selection: "context" },
context: { validation: { status: "verified" } },
});
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("CLI exposes only the allowlisted public Context.dev operations", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-context-cli-"));
const home = path.join(root, "state");
let output = "";
const stream = { write: (value: string) => { output += value; } };
const calls: Array<[string, unknown, unknown]> = [];
const client = {
scrapeMarkdown: async (target: string, options: unknown) => { calls.push(["markdown", target, options]); return { markdown: "hello" }; },
scrapeHtml: async (target: string, options: unknown) => { calls.push(["html", target, options]); return { html: "<p>hello</p>" }; },
crawl: async (target: string, options: unknown) => { calls.push(["crawl", target, options]); return { pages: [] }; },
sitemap: async (target: string, options: unknown) => { calls.push(["sitemap", target, options]); return { links: [] }; },
screenshot: async (target: string, options: unknown) => { calls.push(["screenshot", target, options]); return { image: "omitted" }; },
};
const common = {
env: { GSTACK_HOME: home }, cwd: root, stdout: stream, stderr: stream,
contextClientFactory: () => client,
};
try {
expect(await main(["context", "scrape-markdown", "https://example.com", "--main-content"], common)).toBe(0);
expect(output).toBe("hello\n");
output = "";
expect(await main(["context", "crawl", "https://example.com", "--max-pages", "2", "--max-depth", "1", "--json"], common)).toBe(0);
expect(calls).toContainEqual(["crawl", "https://example.com", { maxPages: 2, maxDepth: 1 }]);
expect(await main(["context", "sitemap", "example.com", "--max-links", "0"], common)).toBe(2);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("public config rejects nested secret-shaped fields", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-context-config-"));
const home = path.join(root, "state");
+300 -5
View File
@@ -2,14 +2,18 @@ import { describe, expect, test } from "bun:test";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { spawn } from "node:child_process";
import { pathToFileURL } from "node:url";
import { main as runtimeMain } from "../runtime/cli.js";
import { summarizeRuntimeBundle } from "../scripts/gstack2/audit-runtime-bundle";
import {
DEFAULT_CAPABILITY_LAUNCHERS,
DEFAULT_RUNTIME_BUNDLE,
DEFAULT_RUNTIME_HELPERS,
MAX_RUNTIME_BUNDLE_BYTES,
defaultBunBuilder,
installManagedRuntime,
normalizeManagedBrowserTree,
runtimeNativePackagePaths,
uninstallManagedRuntime,
runCommand,
@@ -27,12 +31,50 @@ const REPO_ROOT = path.resolve(import.meta.dir, "..");
const FULL_RUNTIME_TEST_TIMEOUT_MS = process.platform === "win32" ? 120_000 : 30_000;
describe("GStack 2 managed runtime installer", () => {
test("browser link normalization accepts internal macOS-style links and rejects escape graphs", async () => {
if (process.platform === "win32") return;
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-browser-links-"));
try {
const valid = path.join(root, "valid");
const version = path.join(valid, "Framework", "Versions", "145");
await fs.mkdir(version, { recursive: true });
await fs.writeFile(path.join(version, "Chrome Framework"), "binary\n", { mode: 0o755 });
await fs.symlink("145", path.join(valid, "Framework", "Versions", "Current"), "dir");
await fs.symlink("Versions/Current/Chrome Framework", path.join(valid, "Framework", "Chrome Framework"), "file");
expect(await normalizeManagedBrowserTree(valid)).toBe(2);
expect((await fs.lstat(path.join(valid, "Framework", "Versions", "Current"))).isDirectory()).toBe(true);
const executable = path.join(valid, "Framework", "Chrome Framework");
expect((await fs.lstat(executable)).isFile()).toBe(true);
expect((await fs.stat(executable)).mode & 0o111).not.toBe(0);
const cases = ["escape", "absolute", "dangling", "loop", "nested-escape"];
for (const name of cases) await fs.mkdir(path.join(root, name));
await fs.writeFile(path.join(root, "outside"), "outside\n");
await fs.symlink("../outside", path.join(root, "escape", "link"));
await fs.symlink(path.join(root, "absolute"), path.join(root, "absolute", "link"));
await fs.symlink("missing", path.join(root, "dangling", "link"));
await fs.symlink("b", path.join(root, "loop", "a"));
await fs.symlink("a", path.join(root, "loop", "b"));
const nestedTarget = path.join(root, "nested-escape", "target");
await fs.mkdir(nestedTarget);
await fs.symlink("../../outside", path.join(nestedTarget, "evil"));
await fs.symlink("target", path.join(root, "nested-escape", "link"), "dir");
for (const name of cases) {
await expect(normalizeManagedBrowserTree(path.join(root, name)))
.rejects.toMatchObject({ code: "INSTALL_BROWSER_PAYLOAD_INVALID" });
}
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("installs, validates, activates, and writes an uninstall-friendly manifest", async () => {
await withFixture(async ({ source, home }) => {
const result = await installFixture(source, home, "2.0.0");
expect(result.pointer.status).toBe("active");
expect(result.pointer.current).toBe("2.0.0");
expect(result.consumedScratch).toBe(true);
expect(await readJson(path.join(home, "versions", "current.json"))).toMatchObject({ current: "2.0.0" });
expect(await fs.readFile(path.join(result.path, "cap", "tool"), "utf8")).toContain("fixture capability");
expect((await fs.lstat(path.join(result.path, "runtime", "cli.js"))).isSymbolicLink()).toBe(false);
@@ -114,6 +156,159 @@ describe("GStack 2 managed runtime installer", () => {
});
});
test("managed Chromium stages transactionally without mutating the source checkout", async () => {
await withFixture(async ({ source, home }) => {
await fs.mkdir(path.join(source, "node_modules", "playwright"), { recursive: true });
await fs.writeFile(path.join(source, "node_modules", "playwright", "cli.js"), "fixture\n");
const entries = [
{ path: "runtime" },
{ path: "bin/gstack", executable: true },
{ path: ".gstack-runtime-browsers", build: "browser" },
];
const phases: string[] = [];
const run = async (_command: string, args: string[], options: { env?: Record<string, string>; superviseTree?: boolean; timeoutMs?: number } = {}) => {
if (!args[0]?.endsWith(path.join("node_modules", "playwright", "cli.js"))) return { code: 0, stdout: "", stderr: "" };
expect(args.slice(1)).toEqual(["install", "--no-shell", "chromium"]);
expect(options.superviseTree).toBe(true);
expect(options.timeoutMs).toBe(15 * 60_000);
const target = options.env?.PLAYWRIGHT_BROWSERS_PATH;
if (!target) throw new Error("missing fixture browser destination");
await fs.mkdir(path.join(target, "chromium-fixture"), { recursive: true });
await fs.writeFile(path.join(target, "chromium-fixture", "chrome"), "fixture\n", { mode: 0o755 });
return { code: 0, stdout: "", stderr: "" };
};
const result = await installManagedRuntime({
sourceDir: source,
home,
version: "browser-transaction",
entries,
capabilities: { browse: ".gstack-runtime-browsers/chromium-fixture/chrome" },
runCommand: run,
smokeTest: async () => {},
onPhase: (event: { phase: string }) => phases.push(event.phase),
});
expect(await exists(path.join(source, ".gstack-runtime-browsers"))).toBe(false);
expect(await exists(path.join(result.path, ".gstack-runtime-browsers", "chromium-fixture", "chrome"))).toBe(true);
expect((await fs.readdir(path.join(home, "tmp"))).some((name) => name.startsWith("install-"))).toBe(false);
expect(phases).toEqual([
"copy-source:start",
"copy-source:complete",
"managed-chromium:start",
"managed-chromium:complete",
"bundle-validation:start",
"bundle-validation:complete",
"activation:start",
"activation:complete",
]);
await expect(installManagedRuntime({
sourceDir: source,
home: path.join(home, "failed"),
version: "browser-failure",
entries,
capabilities: { browse: ".gstack-runtime-browsers/chromium-fixture/chrome" },
runCommand: async () => { throw new Error("download failed"); },
smokeTest: async () => {},
})).rejects.toMatchObject({ code: "INSTALL_BROWSER_DOWNLOAD_FAILED" });
expect(await exists(path.join(source, ".gstack-runtime-browsers"))).toBe(false);
});
});
test("ordinary source installs ignore an untracked browser cache while prepared artifacts retain their verified cache", async () => {
await withFixture(async ({ source, home }) => {
await fs.mkdir(path.join(source, "node_modules", "playwright"), { recursive: true });
await fs.writeFile(path.join(source, "node_modules", "playwright", "cli.js"), "fixture\n");
await fs.mkdir(path.join(source, ".gstack-runtime-browsers", "untrusted"), { recursive: true });
await fs.writeFile(path.join(source, ".gstack-runtime-browsers", "untrusted", "chrome"), "untrusted\n", { mode: 0o755 });
const entries = [
{ path: "runtime" },
{ path: "bin/gstack", executable: true },
{ path: ".gstack-runtime-browsers", build: "browser" },
];
let downloads = 0;
const result = await installManagedRuntime({
sourceDir: source,
home,
version: "browser-source-cache-ignored",
entries,
capabilities: { browse: ".gstack-runtime-browsers/fresh/chrome" },
runCommand: async (_command: string, args: string[], options: { env?: Record<string, string> } = {}) => {
downloads += 1;
expect(args.slice(1)).toEqual(["install", "--no-shell", "chromium"]);
const target = options.env?.PLAYWRIGHT_BROWSERS_PATH;
if (!target) throw new Error("missing fixture browser destination");
await fs.mkdir(path.join(target, "fresh"), { recursive: true });
await fs.writeFile(path.join(target, "fresh", "chrome"), "fresh\n", { mode: 0o755 });
return { code: 0, stdout: "", stderr: "" };
},
smokeTest: async () => {},
});
expect(downloads).toBe(1);
expect(await exists(path.join(result.path, ".gstack-runtime-browsers", "fresh", "chrome"))).toBe(true);
expect(await exists(path.join(result.path, ".gstack-runtime-browsers", "untrusted", "chrome"))).toBe(false);
expect(await fs.readFile(path.join(source, ".gstack-runtime-browsers", "untrusted", "chrome"), "utf8")).toBe("untrusted\n");
let preparedDownloadAttempted = false;
const prepared = await installManagedRuntime({
sourceDir: source,
home: path.join(home, "prepared-home"),
version: "browser-prepared-cache",
entries,
capabilities: { browse: ".gstack-runtime-browsers/untrusted/chrome" },
buildMissing: false,
preparedSource: true,
runCommand: async () => {
preparedDownloadAttempted = true;
throw new Error("prepared artifacts must not download Chromium again");
},
smokeTest: async () => {},
});
expect(preparedDownloadAttempted).toBe(false);
expect(await fs.readFile(path.join(prepared.path, ".gstack-runtime-browsers", "untrusted", "chrome"), "utf8")).toBe("untrusted\n");
});
});
test("ordinary source installs ignore an untracked Bun executable while prepared artifacts retain and probe theirs", async () => {
await withFixture(async ({ source, home }) => {
const relative = path.join(".gstack-runtime-tools", process.platform === "win32" ? "bun.exe" : "bun");
const sourceBun = path.join(source, relative);
await fs.mkdir(path.dirname(sourceBun), { recursive: true });
await fs.writeFile(sourceBun, "untrusted checkout executable\n", { mode: 0o755 });
const entries = [
{ path: "runtime" },
{ path: "bin/gstack", executable: true },
{ path: relative, build: "managed-bun", executable: true },
];
const installed = await installManagedRuntime({
sourceDir: source,
home,
version: "bun-source-cache-ignored",
entries,
capabilities: { bun: relative },
bunCommand: process.execPath,
smokeTest: async () => {},
});
expect(await fs.readFile(sourceBun, "utf8")).toBe("untrusted checkout executable\n");
expect((await fs.stat(path.join(installed.path, relative))).size).toBeGreaterThan(1024 * 1024);
expect(installed.pointer.current).toBe("bun-source-cache-ignored");
await fs.copyFile(process.execPath, sourceBun);
if (process.platform !== "win32") await fs.chmod(sourceBun, 0o755);
const prepared = await installManagedRuntime({
sourceDir: source,
home: path.join(home, "prepared"),
version: "bun-prepared-cache",
entries,
capabilities: { bun: relative },
buildMissing: false,
preparedSource: true,
smokeTest: async () => {},
});
expect(prepared.pointer.current).toBe("bun-prepared-cache");
expect((await readJson(path.join(prepared.path, ".gstack-bundle.json"))).tools.bun.version).toBe("1.3.14");
});
});
test("default capability builds never regenerate the Agent Skills tree", async () => {
const calls: Array<{ command: string; args: string[] }> = [];
await defaultBunBuilder({
@@ -159,7 +354,28 @@ describe("GStack 2 managed runtime installer", () => {
}
await withFixture(async ({ home }) => {
const result = await installManagedRuntime({ sourceDir: REPO_ROOT, home, version: "helper-contract-test" });
const result = await installManagedRuntime({
sourceDir: REPO_ROOT,
home,
version: "helper-contract-test",
runCommand: async (command: string, args: string[], options: { env?: Record<string, string> } = {}) => {
if (args[0] === "--eval" && args[1]?.includes("process.execPath")) {
return { code: 0, stdout: process.execPath, stderr: "" };
}
if (args[0]?.endsWith(path.join("node_modules", "playwright", "cli.js"))) {
const browserRoot = options.env?.PLAYWRIGHT_BROWSERS_PATH;
if (!browserRoot) throw new Error("fixture browser root missing");
await fs.mkdir(path.join(browserRoot, "chromium-fixture"), { recursive: true });
await fs.writeFile(path.join(browserRoot, "chromium-fixture", "chrome"), "fixture\n", { mode: 0o755 });
return { code: 0, stdout: "", stderr: "" };
}
if (args[0] === "--version" && (command === process.execPath || command.includes(".gstack-runtime-tools"))) {
return { code: 0, stdout: "1.3.14\n", stderr: "" };
}
if (args[0] === "--version") return { code: 0, stdout: "v20.18.0\n", stderr: "" };
return { code: 0, stdout: "gstack runtime fixture\n", stderr: "" };
},
});
for (const helper of contract.helpers) {
const stable = path.join(home, "bin", helper.name);
const stat = await fs.lstat(stable);
@@ -180,11 +396,24 @@ describe("GStack 2 managed runtime installer", () => {
const next = await runInstalledLauncher(home, "gstack-next-version", ["--help"], { capture: true });
expect(next.stdout).toContain("Usage: gstack-next-version");
if (process.platform !== "win32") {
const node = (await runCommand("node", ["-p", "process.execPath"], { capture: true })).stdout.trim();
const nodeOnlyEnv = {
...process.env,
PATH: "/usr/bin:/bin",
GSTACK_NODE: node,
BUN_CMD: "",
};
const managedBun = await runInstalledLauncher(home, "bun", ["--version"], { capture: true, env: nodeOnlyEnv });
expect(managedBun.stdout.trim()).toBe("1.3.14");
const nodeOnlyHelper = await runInstalledLauncher(home, "gstack-next-version", ["--help"], { capture: true, env: nodeOnlyEnv });
expect(nodeOnlyHelper.stdout).toContain("Usage: gstack-next-version");
}
const sourced = await runCommand("bash", ["-c", '. "$1"; type read_secret_to_env', "_", path.join(home, "bin", "gstack-gbrain-lib.sh")], { capture: true });
expect(sourced.stdout).toContain("read_secret_to_env");
const syncAlias = await runInstalledLauncher(home, "gstack-gbrain-sync", ["--help"], { capture: true });
expect(`${syncAlias.stdout}${syncAlias.stderr}`).toContain("gstack-gbrain-sync");
const syncTypeScriptAlias = await runCommand("bun", [path.join(home, "bin", "gstack-gbrain-sync.ts"), "--help"], { capture: true });
const syncTypeScriptAlias = await runInstalledLauncher(home, "gstack-gbrain-sync.ts", ["--help"], { capture: true });
expect(`${syncTypeScriptAlias.stdout}${syncTypeScriptAlias.stderr}`).toContain("gstack-gbrain-sync");
const body = path.join(home, "audit-body.txt");
@@ -222,6 +451,57 @@ describe("GStack 2 managed runtime installer", () => {
}
});
test("supervised timeout terminates descendants before returning cleanup authority", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-command-tree-timeout-"));
const marker = path.join(root, "descendant-wrote-after-timeout");
const childProgram = `setTimeout(() => require("node:fs").writeFileSync(${JSON.stringify(marker)}, "unsafe"), 500); setInterval(() => {}, 1000);`;
const parentProgram = `require("node:child_process").spawn(process.execPath, ["--eval", ${JSON.stringify(childProgram)}], { stdio: "ignore" }); setInterval(() => {}, 1000);`;
try {
await expect(runCommand(process.execPath, ["--eval", parentProgram], {
capture: true,
timeoutMs: 100,
killGraceMs: 5_000,
superviseTree: true,
})).rejects.toMatchObject({ code: "INSTALL_COMMAND_TIMEOUT" });
await new Promise((resolve) => setTimeout(resolve, 700));
expect(await exists(marker)).toBe(false);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("supervised cancellation terminates descendants before the installer process exits", async () => {
if (process.platform === "win32") return;
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-command-tree-cancel-"));
const ready = path.join(root, "ready");
const marker = path.join(root, "descendant-wrote-after-cancel");
const childProgram = `setTimeout(() => require("node:fs").writeFileSync(${JSON.stringify(marker)}, "unsafe"), 700); setInterval(() => {}, 1000);`;
const parentProgram = `require("node:child_process").spawn(process.execPath, ["--eval", ${JSON.stringify(childProgram)}], { stdio: "ignore" }); setInterval(() => {}, 1000);`;
const harness = [
`import { runCommand } from ${JSON.stringify(pathToFileURL(path.join(REPO_ROOT, "runtime", "install.js")).href)};`,
`import fs from "node:fs";`,
`fs.writeFileSync(${JSON.stringify(ready)}, "ready");`,
`try { await runCommand(process.execPath, ["--eval", ${JSON.stringify(parentProgram)}], { superviseTree: true, timeoutMs: 60000 }); } catch { process.exitCode = 0; }`,
].join("\n");
try {
const process_ = spawn("node", ["--input-type=module", "--eval", harness], { stdio: "ignore" });
for (let attempt = 0; attempt < 100 && !await exists(ready); attempt += 1) {
await new Promise((resolve) => setTimeout(resolve, 10));
}
expect(await exists(ready)).toBe(true);
await new Promise((resolve) => setTimeout(resolve, 200));
process_.kill("SIGINT");
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("cancellation harness did not exit")), 5_000);
process_.once("exit", () => { clearTimeout(timeout); resolve(null); });
});
await new Promise((resolve) => setTimeout(resolve, 900));
expect(await exists(marker)).toBe(false);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("selects one deterministic native dependency closure per supported host", () => {
expect(runtimeNativePackagePaths({ platform: "darwin", arch: "arm64" })).toEqual([
"node_modules/@img/colour",
@@ -397,6 +677,15 @@ describe("GStack 2 managed runtime installer", () => {
await fs.chmod(cli, originalMode);
}
await fs.writeFile(manifestPath, `${JSON.stringify({
...manifest,
files: manifest.files.map((file: { size: number }, index: number) =>
index === 0 ? { ...file, size: MAX_RUNTIME_BUNDLE_BYTES + 1 } : file),
}, null, 2)}\n`);
await expect(validateRuntimeBundle(result.path, { version: "2.0.0" })).rejects.toMatchObject({
code: "INSTALL_VALIDATION_FAILED",
});
await fs.writeFile(manifestPath, `${JSON.stringify({ ...manifest, files: [] }, null, 2)}\n`);
await expect(validateRuntimeBundle(result.path, { version: "2.0.0" })).rejects.toMatchObject({
code: "INSTALL_VALIDATION_FAILED",
@@ -645,7 +934,11 @@ describe("GStack 2 managed runtime installer", () => {
await fs.writeFile(path.join(root, "package.json"), '{"type":"module","dependencies":{"sharp":"1.0.0"},"devDependencies":{"test-only-sdk":"1.0.0"}}\n');
await fs.writeFile(path.join(root, "node_modules", "sharp", "package.json"), '{"name":"sharp","main":"index.js"}\n');
await fs.writeFile(path.join(root, "node_modules", "sharp", "index.js"), 'module.exports = require("@img/sharp-fixture");\n');
await fs.writeFile(path.join(runtime, "install.js"), 'console.log(`installer=${process.release.name}`);\n');
await fs.writeFile(path.join(runtime, "install.js"), `import { spawnSync } from "node:child_process";
const result = spawnSync(process.env.BUN_CMD || "bun", ["install", "--production", "--frozen-lockfile"], { stdio: "inherit" });
if (result.status !== 0) process.exit(result.status || 1);
console.log(\`installer=\${process.release.name}\`);
`);
await fs.writeFile(path.join(fakeBin, "bun"), `#!/bin/sh
printf '%s\\n' "$*" >> "$BUN_LOG"
mkdir -p "$FIXTURE_ROOT/node_modules/@img/sharp-fixture"
@@ -653,7 +946,7 @@ printf '{"name":"@img/sharp-fixture","main":"index.js"}\\n' > "$FIXTURE_ROOT/nod
printf 'module.exports = {}\\n' > "$FIXTURE_ROOT/node_modules/@img/sharp-fixture/index.js"
`, { mode: 0o755 });
const result = await runCommand(path.join(root, "setup"), [], {
const result = await runCommand(path.join(root, "setup"), ["--install-now", "--yes"], {
capture: true,
env: {
...process.env,
@@ -666,7 +959,7 @@ printf 'module.exports = {}\\n' > "$FIXTURE_ROOT/node_modules/@img/sharp-fixture
expect(await fs.readFile(log, "utf8")).toContain("install --production --frozen-lockfile");
expect(result.stdout).toContain("installer=node");
const second = await runCommand(path.join(root, "setup"), [], {
const second = await runCommand(path.join(root, "setup"), ["--install-now", "--yes"], {
capture: true,
env: {
...process.env,
@@ -756,6 +1049,8 @@ async function createSource(source: string) {
await fs.mkdir(path.join(source, "cap"), { recursive: true });
await fs.writeFile(path.join(source, "package.json"), '{"name":"gstack","version":"2.0.0","type":"module"}\n');
await fs.writeFile(path.join(source, "runtime", "cli.js"), fixtureCli(""));
await fs.writeFile(path.join(source, "runtime", "tooling.js"),
'export async function resolveBashCommand(env = process.env) { return env.GSTACK_BASH || "bash"; }\n');
await fs.writeFile(path.join(source, "bin", "gstack"), `#!/usr/bin/env node
import { main } from "../runtime/cli.js";
process.exitCode = await main(process.argv.slice(2));
+70 -1
View File
@@ -8,12 +8,14 @@ import {
ensureManagedHome,
purgeManagedHomeUnlocked,
setupRuntime,
stageUpgrade,
uninstallManagedRuntime,
} from "../runtime/index.js";
const roots: string[] = [];
const configBin = path.resolve(import.meta.dir, "../bin/gstack-config");
const gstackBin = path.resolve(import.meta.dir, "../bin/gstack");
const updateCheckBin = path.resolve(import.meta.dir, "../bin/gstack-update-check");
async function root() {
const result = await fs.mkdtemp(path.join(os.tmpdir(), "gstack2-safety-config-"));
@@ -26,6 +28,29 @@ afterEach(async () => {
});
describe("managed-home destructive boundary", () => {
test("rejects symlinked tmp and versions directories before runtime mutation", async () => {
if (process.platform === "win32") return;
const base = await root();
const home = path.join(base, "home");
const outside = path.join(base, "outside");
await ensureManagedHome(home);
await fs.mkdir(outside);
await fs.writeFile(path.join(outside, "keep"), "keep\n");
await fs.symlink(outside, path.join(home, "tmp"), "dir");
await expect(uninstallManagedRuntime(home)).rejects.toMatchObject({ code: "MANAGED_HOME_SUBDIRECTORY_UNSAFE" });
await fs.rm(path.join(home, "tmp"));
const source = path.join(base, "source");
await fs.mkdir(source);
await fs.writeFile(path.join(source, "payload"), "payload\n");
await fs.symlink(outside, path.join(home, "versions"), "dir");
await expect(stageUpgrade({ home, sourceDir: source, version: "1.0.0" }))
.rejects.toMatchObject({ code: "MANAGED_HOME_SUBDIRECTORY_UNSAFE" });
expect(await fs.readFile(path.join(outside, "keep"), "utf8")).toBe("keep\n");
expect(await fs.readdir(outside)).toEqual(["keep"]);
});
test("claims only a new or empty directory and leaves nonempty input untouched", async () => {
const base = await root();
const occupied = path.join(base, "occupied");
@@ -162,6 +187,32 @@ describe("managed-home destructive boundary", () => {
});
describe("one config authority", () => {
test("Agent Skills owns updates and passive GStack release requests are off", async () => {
const base = await root();
const home = path.join(base, "state");
const remoteVersion = path.join(base, "remote-version");
await fs.writeFile(remoteVersion, "999.0.0\n");
const config = spawnSync(process.execPath, [configBin, "get", "update_check"], {
encoding: "utf8",
env: { ...process.env, GSTACK_HOME: home },
});
expect(config.status).toBe(0);
expect(config.stdout).toBe("false");
const passive = spawnSync(updateCheckBin, [], {
encoding: "utf8",
env: {
...process.env,
GSTACK_HOME: home,
GSTACK_REMOTE_URL: `file://${remoteVersion}`,
},
});
expect(passive.status).toBe(0);
expect(passive.stdout).toBe("");
expect(await fs.stat(path.join(home, "last-update-check")).catch(() => null)).toBeNull();
});
test("compatibility helper and runtime config share config.json", async () => {
const home = path.join(await root(), "state");
const run = (args: string[]) => spawnSync(process.execPath, [configBin, ...args], {
@@ -194,7 +245,8 @@ describe("one config authority", () => {
expect(JSON.parse(await fs.readFile(path.join(home, "config.json"), "utf8")).telemetry).toBe("anonymous");
const setup = run(["setup"]);
expect(setup.status).toBe(0);
expect(setup.stdout).toContain("gstack is ready");
expect(setup.stdout).toContain("GStack state initialized");
expect(setup.stdout).toContain("optional runtime: unchanged");
});
test("legacy YAML is read-only migration input and JSON takes authority on write", async () => {
@@ -231,4 +283,21 @@ describe("one config authority", () => {
expect(install).not.toContain('"gstack-team-init": helper');
expect(config).not.toMatch(/\.claude\/skills|gstack-relink|gen:skill-docs:user/);
});
test("runtime remediation never assumes a host-specific skill path or legacy setup script", async () => {
const sources = [
"runtime/install.js",
"make-pdf/src/setup.ts",
"make-pdf/src/browseClient.ts",
"make-pdf/src/diagram-prepass.ts",
];
for (const relative of sources) {
const source = await fs.readFile(path.resolve(import.meta.dir, "..", relative), "utf8");
expect(source, relative).not.toMatch(/(?:re-?run|run)\s+`?\.\/setup/i);
expect(source, relative).not.toMatch(/(?:to fix|missing|unsafe|invalid)[\s\S]{0,240}(?:~\/)?\.claude\/skills/i);
}
const pdfSetup = await fs.readFile(path.resolve(import.meta.dir, "../make-pdf/src/setup.ts"), "utf8");
expect(pdfSetup).toContain("gstack doctor --skill-api 2.0");
expect(pdfSetup).toContain("--capability pdf");
});
});
+415
View File
@@ -0,0 +1,415 @@
import { describe, expect, test } from "bun:test";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { runDoctor } from "../runtime/doctor.js";
import { runInstallerCli, runtimeSlotVersion, runtimeSurfaceForCapabilities } from "../runtime/install.js";
import { resolveRuntimePaths } from "../runtime/paths.js";
import { setupRuntime } from "../runtime/setup.js";
import { bashCandidates, resolveBashCommand } from "../runtime/tooling.js";
import {
BOOTSTRAP_RUNTIME_VERSION,
OFFICIAL_MANIFEST_URL,
main as bootstrapMain,
} from "../runtime/runtime-bootstrap.mjs";
function capture() {
let value = "";
return { stream: { write: (chunk: string) => { value += chunk; } }, value: () => value };
}
describe("GStack runtime setup UX", () => {
test("capability selection keeps the core and excludes unselected heavyweight surfaces", () => {
const surface = runtimeSurfaceForCapabilities(["browser"]);
const paths = surface.entries.map((entry) => entry.path);
expect(paths).toContain("runtime");
expect(paths.some((entry) => entry.startsWith("browse/"))).toBe(true);
expect(paths.some((entry) => entry.startsWith("design/"))).toBe(false);
expect(paths.some((entry) => entry.startsWith("make-pdf/"))).toBe(false);
expect(surface.capabilities.browse).toBeTruthy();
expect(surface.capabilities["gstack-design"]).toBeUndefined();
for (const target of Object.values(surface.capabilities)) {
expect(paths.some((root) => target === root || target.startsWith(`${root}/`))).toBe(true);
}
const core = runtimeSurfaceForCapabilities([]);
const corePaths = core.entries.map((entry) => entry.path);
for (const target of Object.values(core.capabilities)) {
expect(corePaths.some((root) => target === root || target.startsWith(`${root}/`))).toBe(true);
}
expect(runtimeSlotVersion("2.0.0", ["pdf", "browser"]))
.toBe(runtimeSlotVersion("2.0.0", ["browser", "pdf"]));
expect(runtimeSlotVersion("2.0.0", ["browser"]))
.not.toBe(runtimeSlotVersion("2.0.0", ["browser", "pdf"]));
const expected = {
browser: ["browser"],
design: ["design"],
diagram: ["browser", "diagram"],
pdf: ["browser", "diagram", "pdf"],
...(process.platform === "darwin" ? { ios: ["ios"] } : {}),
};
for (const [capability, dependencies] of Object.entries(expected)) {
expect(runtimeSurfaceForCapabilities([capability]).selected).toEqual(dependencies);
}
});
test("later capability installs retain already approved capabilities unless replacement is explicit", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-setup-retain-"));
const home = path.join(root, "home");
const source = path.resolve(import.meta.dir, "..");
const paths = resolveRuntimePaths({ home });
const active = path.join(paths.versions, "existing");
try {
await fs.mkdir(active, { recursive: true });
await fs.writeFile(paths.versionPointer, JSON.stringify({
schemaVersion: 2, status: "active", current: "existing", lastKnownGood: "existing",
}));
await fs.writeFile(path.join(active, ".gstack-bundle.json"), JSON.stringify({
selectedCapabilities: ["design"],
}));
const retained = capture();
expect(await runInstallerCli([
"--source", source, "--home", home, "--capabilities", "pdf", "--dry-run", "--json",
], { stdout: retained.stream, stderr: retained.stream })).toBe(0);
expect(JSON.parse(retained.value()).preview.capabilities).toEqual(["browser", "design", "diagram", "pdf"]);
const replaced = capture();
expect(await runInstallerCli([
"--source", source, "--home", home, "--capabilities", "pdf", "--replace-capabilities", "--dry-run", "--json",
], { stdout: replaced.stream, stderr: replaced.stream })).toBe(0);
expect(JSON.parse(replaced.value()).preview.capabilities).toEqual(["browser", "diagram", "pdf"]);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("dry-run previews capabilities and exact bytes without creating state", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-setup-preview-"));
const home = path.join(root, "home");
const source = path.resolve(import.meta.dir, "..");
const output = capture();
try {
expect(await runInstallerCli([
"--source", source,
"--home", home,
"--capabilities", "core",
"--dry-run",
"--json",
], { stdout: output.stream, stderr: output.stream })).toBe(0);
const result = JSON.parse(output.value());
expect(result).toMatchObject({ ok: true, action: "dry-run", mutated: false });
expect(result.preview.capabilities).toEqual([]);
expect(result.preview.bytes).toBeGreaterThan(0);
expect(result.preview.materializations.find((item) => item.kind === "managed-bun-capture")).toMatchObject({
available: true,
version: "1.3.14",
});
await expect(fs.stat(home)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("Node-only dry-run explains a missing source-build Bun without mutating or failing", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-node-only-preview-"));
const home = path.join(root, "home");
const output = capture();
try {
expect(await runInstallerCli([
"--source", path.resolve(import.meta.dir, ".."),
"--home", home,
"--capabilities", "browser",
"--dry-run",
"--json",
], {
stdout: output.stream,
stderr: output.stream,
env: { ...process.env, BUN_CMD: "definitely-missing-bun" },
installOptions: { runCommand: async () => { throw new Error("Bun absent"); } },
})).toBe(0);
const preview = JSON.parse(output.value()).preview;
expect(preview.materializations.find((item) => item.kind === "managed-bun-capture")).toMatchObject({
available: false,
command: "definitely-missing-bun",
});
expect(preview.materializations.find((item) => item.kind === "playwright-chromium-download")).toBeTruthy();
await expect(fs.stat(home)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("Windows Bash discovery shared by doctor and launchers finds a standard Git installation", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-git-bash-"));
const bash = path.join(root, "Git", "bin", "bash.exe");
try {
await fs.mkdir(path.dirname(bash), { recursive: true });
await fs.writeFile(bash, "fixture\n");
const env = { ProgramFiles: root };
expect(bashCandidates(env, "win32")).toContain(bash);
expect(await resolveBashCommand(env, "win32")).toBe(bash);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("state initialization does not make doctor claim the runtime is installed", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-doctor-missing-runtime-"));
const home = path.join(root, "home");
try {
await setupRuntime({ home, cwd: root });
const report = await runDoctor({ home, cwd: root, nodeCommand: process.execPath });
expect(report.ok).toBe(false);
expect(report.checks.find((check) => check.id === "managed-runtime")).toMatchObject({ status: "fail" });
expect(report.checks.find((check) => check.id === "capability:browser")).toMatchObject({ status: "warn" });
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("doctor fails closed when installed skills require a different runtime API", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-doctor-api-"));
const home = path.join(root, "home");
try {
await setupRuntime({ home, cwd: root });
const paths = resolveRuntimePaths({ home });
const active = path.join(paths.versions, "fixture");
await fs.mkdir(active, { recursive: true });
await fs.writeFile(path.join(active, ".gstack-bundle.json"), JSON.stringify({
compatibility: { skillApi: "2.0" },
selectedCapabilities: [],
capabilities: {},
}));
await fs.writeFile(paths.versionPointer, JSON.stringify({
schemaVersion: 2, status: "active", current: "fixture", lastKnownGood: "fixture",
}));
const report = await runDoctor({
home, cwd: root, nodeCommand: process.execPath, expectedSkillApi: "3.0",
});
expect(report.ok).toBe(false);
expect(report.checks.find((check) => check.id === "managed-runtime")).toMatchObject({
status: "fail",
details: { expectedSkillApi: "3.0" },
});
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("doctor rejects selected capabilities whose declared dependencies are absent", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-doctor-deps-"));
const home = path.join(root, "home");
try {
await setupRuntime({ home, cwd: root });
const paths = resolveRuntimePaths({ home });
const active = path.join(paths.versions, "fixture");
await fs.mkdir(active, { recursive: true });
await fs.writeFile(path.join(active, ".gstack-bundle.json"), JSON.stringify({
compatibility: { skillApi: "2.0" },
selectedCapabilities: ["pdf"],
capabilities: { "make-pdf": "make-pdf/dist/pdf" },
}));
await fs.writeFile(paths.versionPointer, JSON.stringify({
schemaVersion: 2, status: "active", current: "fixture", lastKnownGood: "fixture",
}));
const report = await runDoctor({ home, cwd: root, nodeCommand: process.execPath });
expect(report.ok).toBe(false);
expect(report.checks.find((check) => check.id === "capability:pdf")).toMatchObject({ status: "fail" });
expect(report.checks.find((check) => check.id === "capability:pdf")?.message).toContain("browser, diagram");
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("doctor resolves the exact Chromium executable from the managed slot", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-doctor-browser-"));
const home = path.join(root, "home");
try {
await setupRuntime({ home, cwd: root });
const paths = resolveRuntimePaths({ home });
const active = path.join(paths.versions, "fixture");
const browserRoot = path.join(active, ".gstack-runtime-browsers");
const managedBun = path.join(active, ".gstack-runtime-tools", process.platform === "win32" ? "bun.exe" : "bun");
const playwright = path.join(active, "node_modules", "playwright");
const executable = path.join(browserRoot, "chromium-fixture", process.platform === "win32" ? "chrome.exe" : "chrome");
await fs.mkdir(path.dirname(executable), { recursive: true });
await fs.mkdir(path.dirname(managedBun), { recursive: true });
await fs.mkdir(playwright, { recursive: true });
await fs.writeFile(executable, "fixture\n", { mode: 0o755 });
await fs.copyFile(process.execPath, managedBun);
if (process.platform !== "win32") await fs.chmod(managedBun, 0o755);
await fs.writeFile(path.join(playwright, "index.mjs"),
`export const chromium = { executablePath: () => ${JSON.stringify(executable)} };\n`);
await fs.writeFile(path.join(active, ".gstack-bundle.json"), JSON.stringify({
compatibility: { skillApi: "2.0" },
selectedCapabilities: ["browser"],
capabilities: { browse: "browse/dist/browse" },
tools: { bun: { path: path.relative(active, managedBun).split(path.sep).join("/"), version: "1.3.14" } },
}));
await fs.writeFile(paths.versionPointer, JSON.stringify({
schemaVersion: 2, status: "active", current: "fixture", lastKnownGood: "fixture",
}));
const report = await runDoctor({ home, cwd: root, nodeCommand: process.execPath });
expect(report.checks.find((check) => check.id === "capability:browser")).toMatchObject({
status: "pass",
details: { executable },
});
expect(report.checks.find((check) => check.id === "runtime-tool:bun")).toMatchObject({ status: "pass" });
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("bootstrap help has no dependency or network side effects", async () => {
const output = capture();
let fetches = 0;
expect(await bootstrapMain(["--help"], {
stdout: output.stream,
stderr: output.stream,
fetch: async () => { fetches += 1; throw new Error("unexpected fetch"); },
})).toBe(0);
expect(output.value()).toContain("--capability");
expect(fetches).toBe(0);
});
test("bootstrap executes through a symlinked or aliased filesystem path", async () => {
if (process.platform === "win32") return;
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-bootstrap-link-"));
try {
const linked = path.join(root, "runtime-bootstrap.mjs");
await fs.symlink(path.resolve(import.meta.dir, "../runtime/runtime-bootstrap.mjs"), linked);
const result = spawnSync(process.execPath, [linked, "--help"], { encoding: "utf8" });
expect(result.status).toBe(0);
expect(result.stdout).toContain("Usage: node runtime-bootstrap.mjs");
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test("bootstrap refuses an artifact whose SHA-256 does not match the official manifest", async () => {
const output = capture();
const target = `${process.platform === "win32" ? "windows" : process.platform}-${process.arch}`;
const artifactUrl = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/fixture.tar.gz`;
let calls = 0;
const fetch_ = async (url: string) => {
calls += 1;
if (url === OFFICIAL_MANIFEST_URL) {
return {
ok: true,
url,
json: async () => ({
schemaVersion: 1,
version: BOOTSTRAP_RUNTIME_VERSION,
skillApi: "2.0",
artifacts: { [target]: { url: artifactUrl, sha256: "0".repeat(64), bytes: 8, format: "tar.gz" } },
}),
};
}
return {
ok: true,
url: artifactUrl,
arrayBuffer: async () => new TextEncoder().encode("tampered").buffer,
};
};
expect(await bootstrapMain(["install", "--capability", "browser"], {
stdout: output.stream,
stderr: output.stream,
fetch: fetch_,
})).toBe(1);
expect(calls).toBe(2);
expect(output.value()).toContain("SHA-256 mismatch");
});
test("bootstrap rejects physical iOS on non-macOS before any network request", async () => {
const output = capture();
let fetches = 0;
expect(await bootstrapMain(["install", "--capability", "ios"], {
platform: "linux",
arch: "x64",
stdout: output.stream,
stderr: output.stream,
fetch: async () => { fetches += 1; throw new Error("unexpected fetch"); },
})).toBe(1);
expect(fetches).toBe(0);
expect(output.value()).toContain("only on macOS");
});
test("official Linux bootstrap rejects musl explicitly before any network request", async () => {
const output = capture();
let fetches = 0;
expect(await bootstrapMain(["install", "--capability", "browser"], {
platform: "linux",
arch: "x64",
libc: "musl",
stdout: output.stream,
stderr: output.stream,
fetch: async () => { fetches += 1; throw new Error("unexpected fetch"); },
})).toBe(1);
expect(fetches).toBe(0);
expect(output.value()).toContain("require glibc Linux");
});
test("declared Cosign bundles must bind the official release workflow identity", async () => {
const output = capture();
const target = `${process.platform === "win32" ? "windows" : process.platform}-${process.arch}`;
const artifactUrl = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/fixture.tar.gz`;
let calls = 0;
expect(await bootstrapMain(["install", "--capability", "browser"], {
stdout: output.stream,
stderr: output.stream,
fetch: async (url: string) => {
calls += 1;
return {
ok: true,
url,
json: async () => ({
schemaVersion: 1,
version: BOOTSTRAP_RUNTIME_VERSION,
skillApi: "2.0",
artifacts: {
[target]: {
url: artifactUrl,
sha256: "0".repeat(64),
bytes: 8,
format: "tar.gz",
cosignBundleUrl: `${artifactUrl}.sigstore.json`,
},
},
}),
};
},
})).toBe(1);
expect(calls).toBe(1);
expect(output.value()).toContain("does not bind the official GStack release workflow");
});
test("developer source fallback is explicit and does not mark the source as a prepared release", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-bootstrap-source-"));
const runtime = path.join(root, "runtime");
const log = path.join(root, "args.json");
const output = capture();
try {
await fs.mkdir(runtime);
await fs.writeFile(path.join(runtime, "install.js"),
`import fs from "node:fs"; fs.writeFileSync(process.env.BOOTSTRAP_TEST_LOG, JSON.stringify(process.argv.slice(2)));\n`);
const previous = process.env.BOOTSTRAP_TEST_LOG;
process.env.BOOTSTRAP_TEST_LOG = log;
try {
expect(await bootstrapMain([
"install", "--source", root, "--capability", "pdf", "--home", path.join(root, "home"),
], { stdout: output.stream, stderr: output.stream })).toBe(0);
} finally {
if (previous == null) delete process.env.BOOTSTRAP_TEST_LOG;
else process.env.BOOTSTRAP_TEST_LOG = previous;
}
const args = JSON.parse(await fs.readFile(log, "utf8"));
expect(args).toContain("--install-now");
expect(args).toContain("--yes");
expect(args).toContain("browser,diagram,pdf");
expect(args).not.toContain("--prepared");
expect(output.value()).toContain("Developer-only source install");
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
});
+16
View File
@@ -105,6 +105,22 @@ describe("gstack 2 upgrade, migration, and cleanup", () => {
expect(await readJson(paths.versionPointer)).toMatchObject({ status: "active", current: "2.0.0" });
});
test("public upgrades never consume caller-owned input even when given the internal option name", async () => {
const root = await temporaryRoot();
const home = path.join(root, "state");
const source = path.join(root, "caller-source");
await fs.mkdir(source);
await fs.writeFile(path.join(source, "keep"), "caller-owned\n");
const result = await stageUpgrade({
home,
sourceDir: source,
version: "1.0.0",
consumeInstallerScratch: true,
});
expect(result.consumedSource).toBe(false);
expect(await fs.readFile(path.join(source, "keep"), "utf8")).toBe("caller-owned\n");
});
test("raw staging rejects empty and symlinked source directories", async () => {
const root = await temporaryRoot();
const home = path.join(root, "state");
+176
View File
@@ -0,0 +1,176 @@
import { afterEach, describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as fsp from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import { pathToFileURL } from 'node:url';
import { SOURCE_ASSIGNMENTS } from '../scripts/gstack2/assignments';
import {
ROOT,
legacySections,
renderLegacyBody,
renderPortedLegacyBody,
renderPortedLegacySection,
retiredInvocationPattern,
} from '../scripts/gstack2/render-legacy';
import { TREE_NAMES } from '../scripts/gstack2/types';
const temporary: string[] = [];
afterEach(async () => {
await Promise.all(temporary.splice(0).map((entry) => fsp.rm(entry, { recursive: true, force: true })));
});
function ownerModule(source: string): string {
const assignment = SOURCE_ASSIGNMENTS.find((entry) => entry.source === source);
if (!assignment) throw new Error(`Unknown source ${source}`);
return path.join(ROOT, 'skills', assignment.tree, 'references', 'legacy', `${source}.md`);
}
describe('GStack 2 canonical skill UX', () => {
test('keeps exactly six public skills while excluding the retired shared onboarding machine', () => {
const publicSkills = fs.readdirSync(path.join(ROOT, 'skills'), { withFileTypes: true })
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(ROOT, 'skills', entry.name, 'SKILL.md')))
.map((entry) => entry.name)
.sort();
expect(publicSkills).toEqual([...TREE_NAMES].sort());
for (const assignment of SOURCE_ASSIGNMENTS) {
const body = fs.readFileSync(ownerModule(assignment.source), 'utf8');
expect(body, assignment.source).not.toContain('## Preamble (run first)');
expect(body, assignment.source).not.toMatch(
/MODEL_OVERLAY: claude|CLAUDE_PLAN_FILE|Add routing rules to CLAUDE\.md|Boil the Ocean principle|TEL_PROMPTED|PROACTIVE_PROMPTED/,
);
expect(body, assignment.source).not.toContain('cd <SKILL_DIR> && ./setup');
}
});
test('resolves retired user-facing recommendations without rewriting package paths', () => {
for (const assignment of SOURCE_ASSIGNMENTS) {
const body = fs.readFileSync(ownerModule(assignment.source), 'utf8');
expect(body, assignment.source).not.toMatch(retiredInvocationPattern());
}
expect(fs.readFileSync(ownerModule('plan-ceo-review'), 'utf8'))
.toContain('$plan --mode Discovery --module office-hours');
expect(fs.readFileSync(ownerModule('open-gstack-browser'), 'utf8'))
.toContain('$GSTACK_BIN/gstack runtime path extension');
});
test('packages one consent-first host-neutral bootstrap with every selected skill', () => {
const source = fs.readFileSync(path.join(ROOT, 'runtime', 'runtime-bootstrap.mjs'));
for (const tree of TREE_NAMES) {
const runtime = fs.readFileSync(path.join(ROOT, 'skills', tree, 'references', 'RUNTIME.md'), 'utf8');
const bootstrap = fs.readFileSync(path.join(ROOT, 'skills', tree, 'references', 'support', 'runtime-bootstrap.mjs'));
const contract = JSON.parse(fs.readFileSync(path.join(ROOT, 'skills', tree, 'references', 'support', 'runtime-contract.json'), 'utf8'));
expect(bootstrap, tree).toEqual(source);
expect(contract, tree).toEqual({ schemaVersion: 1, runtimeVersion: '2.0.0', skillApi: '2.0' });
expect(runtime, tree).toContain('preview --capability <name>');
expect(runtime, tree).toContain('It never downloads components or mutates runtime state.');
expect(runtime, tree).toContain('install --capability <name> --yes');
expect(runtime, tree).toContain('Never run `./setup` inside a standard-installed skill directory');
expect(runtime, tree).toContain('Deferring installation records no consent');
expect(runtime, tree).toContain('Logical `browser` expands to `browser-code + browser-headless`');
expect(runtime, tree).toContain('`browser-visible` expands to `browser-code + browser-visible` and does not require headless');
expect(runtime, tree).toContain('`pdf` depends on `diagram`');
expect(runtime, tree).toContain('`all` means those five and intentionally excludes visible Chromium');
expect(runtime, tree).toContain('summed compressed bytes');
expect(runtime, tree).toContain('B=$GSTACK_BIN/browse');
expect(runtime, tree).toContain('BUN_CMD=$GSTACK_BIN/bun');
expect(runtime, tree).toContain('discovers Git for Windows Bash');
expect(runtime, tree).toContain('Python is not a global GStack prerequisite');
}
});
test('defers visible Chromium until a workflow reaches a headed point of use', () => {
for (const source of ['open-gstack-browser', 'pair-agent', 'setup-browser-cookies']) {
const body = fs.readFileSync(ownerModule(source), 'utf8');
expect(body, source).toContain('## Visible-browser point-of-use gate');
expect(body, source).toContain('preview --capability browser-visible');
expect(body, source).toContain('install --capability browser-visible --yes');
expect(body, source).toContain('never requires `browser-headless`');
}
expect(fs.readFileSync(ownerModule('browse'), 'utf8')).not.toContain('browser-visible');
expect(fs.readFileSync(ownerModule('qa-only'), 'utf8')).not.toContain('browser-visible');
});
test('binds retained runtime helper variables to stable host-neutral paths', () => {
for (const assignment of SOURCE_ASSIGNMENTS) {
const body = fs.readFileSync(ownerModule(assignment.source), 'utf8');
expect(body, assignment.source).not.toMatch(/bun\.sh\/install|bun run \$GSTACK_BIN|command -v bun/);
if (!/\$(?:GSTACK_BIN|GSTACK_ROOT|GSTACK_STATE_ROOT)\b|\$(?:B|D|P)\b/.test(body)) continue;
expect(body, assignment.source).toContain('## Host-neutral runtime bindings');
expect(body, assignment.source).toContain('GSTACK_BIN="$GSTACK_HOME/bin"');
expect(body, assignment.source).toContain('BUN_CMD="$GSTACK_BIN/bun"');
expect(body, assignment.source).toContain('B="$GSTACK_BIN/browse"');
}
});
test('the packaged post-approval bootstrap reaches the managed installer without enrolling a host', async () => {
const root = await fsp.mkdtemp(path.join(os.tmpdir(), 'gstack-skill-bootstrap-'));
temporary.push(root);
const source = path.join(root, 'reviewed checkout');
const home = path.join(root, 'runtime home');
await fsp.mkdir(path.join(source, 'runtime'), { recursive: true });
await fsp.writeFile(path.join(source, 'runtime', 'install.js'), [
'import fs from "node:fs";',
'import path from "node:path";',
'const home = process.argv[process.argv.indexOf("--home") + 1];',
'fs.mkdirSync(home, { recursive: true });',
'fs.writeFileSync(path.join(home, "argv.json"), JSON.stringify(process.argv.slice(2)));',
].join('\n'));
const packaged = path.join(ROOT, 'skills', 'qa', 'references', 'support', 'runtime-bootstrap.mjs');
const module = await import(`${pathToFileURL(packaged).href}?test=${Date.now()}`);
let stdout = '';
let stderr = '';
const code = await module.main([
'install', '--source', source, '--capability', 'browser', '--home', home, '--yes',
], {
stdout: { write: (chunk: string) => { stdout += chunk; } },
stderr: { write: (chunk: string) => { stderr += chunk; } },
});
expect(code).toBe(0);
expect(stderr).toContain('Developer-only source install');
expect(stdout).toContain('No coding host was enrolled');
expect(JSON.parse(await fsp.readFile(path.join(home, 'argv.json'), 'utf8'))).toContain('--install-now');
});
test('cuts canonical active prompt bytes by at least half without dropping carved specialist phases', () => {
const baselineBytes = SOURCE_ASSIGNMENTS.reduce(
(total, assignment) => total + Buffer.byteLength(renderLegacyBody(assignment.source)),
0,
);
const canonicalBytes = SOURCE_ASSIGNMENTS.reduce(
(total, assignment) => total + Buffer.byteLength(renderPortedLegacyBody(assignment.source)),
0,
);
expect(canonicalBytes).toBeLessThan(baselineBytes * 0.5);
for (const section of legacySections()) {
const assignment = SOURCE_ASSIGNMENTS.find((entry) => entry.source === section.source)!;
const filename = path.basename(section.relativePath).replace(/\.tmpl$/, '');
const reference = `references/sections/${section.source}/${filename}`;
const module = fs.readFileSync(ownerModule(section.source), 'utf8');
const packaged = fs.readFileSync(path.join(ROOT, 'skills', assignment.tree, reference), 'utf8');
expect(module, section.relativePath).toContain(reference);
expect(packaged.trim(), section.relativePath).toBe(renderPortedLegacySection(section).trim());
expect(packaged, section.relativePath).not.toMatch(retiredInvocationPattern());
}
for (const tree of TREE_NAMES) {
const artifactRoot = path.join(ROOT, 'skills', tree, 'references', 'artifacts');
if (!fs.existsSync(artifactRoot)) continue;
const pending = [artifactRoot];
while (pending.length) {
const current = pending.pop()!;
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
const target = path.join(current, entry.name);
if (entry.isDirectory()) pending.push(target);
else if (entry.name.endsWith('.md')) {
expect(fs.readFileSync(target, 'utf8'), path.relative(ROOT, target)).not.toMatch(retiredInvocationPattern());
}
}
}
}
}, 15_000);
});
+11
View File
@@ -41,6 +41,17 @@ describe('GStack 2 structured dispatch', () => {
expect(localSpec.mutation).toBe('spec-only');
});
test('missing review mutation authority fails closed', () => {
for (const audit_focus of ['broad', 'performance', 'deep']) {
const review = routeStructured({ audit_focus });
expect(review.tree, audit_focus).toBe('review');
expect(review.mutation, audit_focus).toBe('report-only');
}
expect(routeStructured({ audit_focus: 'broad', mutation_authorized: true }).mutation)
.toBe('fix-safe');
});
test('system-functional QA loads preserved report/fix and root-cause modules', () => {
expect(routeStructured({ surface: 'developer-workflow', channels: ['cli', 'api'], mutation_authorized: false }))
.toMatchObject({
+120
View File
@@ -0,0 +1,120 @@
import { describe, expect, test } from "bun:test";
import fs from "node:fs";
import path from "node:path";
import { createHash } from "node:crypto";
const ROOT = path.resolve(import.meta.dir, "..");
const read = (relative: string) => fs.readFileSync(path.join(ROOT, relative), "utf8");
describe("release and CI hardening", () => {
test("every workflow has explicit permissions and immutable action refs", () => {
const workflowRoot = path.join(ROOT, ".github", "workflows");
for (const name of fs.readdirSync(workflowRoot).filter((entry) => entry.endsWith(".yml"))) {
const source = fs.readFileSync(path.join(workflowRoot, name), "utf8");
expect(source, `${name} must declare top-level permissions`).toMatch(/^permissions:\s*$/m);
for (const match of source.matchAll(/\buses:\s*[^\s@]+@([^\s#]+)/g)) {
expect(match[1], `${name} contains a mutable action ref`).toMatch(/^[a-f0-9]{40}$/);
}
}
});
test("paid eval secrets cannot run against fork PR code", () => {
const source = read(".github/workflows/evals.yml");
const guard = "github.event.pull_request.head.repo.full_name == github.repository";
expect(source.match(new RegExp(guard.replaceAll(".", "\\."), "g"))?.length).toBeGreaterThanOrEqual(3);
});
test("npm package is an explicit small runtime-control surface", () => {
const pkg = JSON.parse(read("package.json"));
expect(pkg.version).toBe(read("VERSION").trim());
expect(pkg.gstack).toEqual({ packageRole: "runtime-control", runtimeVersion: "2.0.0", skillApi: "2.0" });
expect(pkg.bin).toEqual({
gstack: "./bin/gstack",
"gstack-runtime-bootstrap": "./runtime/runtime-bootstrap.mjs",
});
expect(pkg.files).toEqual(["bin/gstack", "runtime", "README.md", "LICENSE", "VERSION"]);
expect(pkg.dependencies["puppeteer-core"]).toBeUndefined();
});
test("runtime identity is aligned independently of the legacy four-slot release counter", () => {
for (const file of ["runtime/index.js", "runtime/install.js", "runtime/runtime-bootstrap.mjs"]) {
expect(read(file), file).toContain('"2.0.0"');
}
expect(read("docs/gstack-2/RELEASE-INTEGRITY.md")).toContain("intentionally different namespaces");
});
test("release workflow emits all six signed byte-counted artifacts", () => {
const workflow = read(".github/workflows/release-artifacts.yml");
for (const target of ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "windows-arm64", "windows-x64"]) {
expect(workflow).toContain(`target: ${target}`);
}
expect(workflow).toContain("cosign sign-blob --yes --bundle");
expect(workflow).toContain("actions/attest-build-provenance@");
expect(workflow).toContain("versions/current.json");
expect(workflow).not.toContain('active="$GSTACK_HOME/versions/2.0.0"');
expect(workflow).toContain(".gstack-runtime-browsers");
expect(workflow).toContain('chromium.launch({ headless: true, channel: "chromium" })');
expect(workflow).not.toContain("--with-deps");
expect(workflow).toContain(".gstack-runtime-tools/bun");
expect(workflow).toContain('"$GSTACK_HOME/bin/bun" --version');
expect(workflow).toContain("BUN-LICENSE-1.3.14.md");
expect(workflow).toContain("command -v bun");
expect(workflow).toContain("GSTACK_NODE=\"$node_command\"");
expect(workflow).toContain("goto about:blank");
const manifest = read(".github/scripts/create-runtime-release-manifest.mjs");
expect(manifest).toContain("bytes: stat.size");
expect(manifest).toContain('certificateOidcIssuer: "https://token.actions.githubusercontent.com"');
});
test("redistributed Bun is pinned and carries the exact tagged license inventory", () => {
const workflow = read(".github/workflows/release-artifacts.yml");
expect(workflow).toContain("bun-version: 1.3.14");
const license = read("runtime/licenses/BUN-LICENSE-1.3.14.md");
expect(createHash("sha256").update(license).digest("hex"))
.toBe("2cb858b2db8fc793bca2093489c5bc8eee615d002cc4924254904044c27a0afa");
const source = read("runtime/licenses/BUN-SOURCE.md");
expect(source).toContain("2c6160ec8fb853f7e8f97d9b249e756c9b0ac44860a68b6bf4f1b0bcbc5c3741");
expect(source).toContain("bun-v1.3.14");
const installer = read("runtime/install.js");
expect(installer).toContain('entry("runtime")');
expect(installer).toContain('entry(managedBunRelativePath(), "managed-bun", true)');
const browser = read("browse/src/cli.ts");
expect(browser).toContain("Every installed/compiled client must use the adjacent Node-compatible daemon");
expect(browser).toContain("if (IS_COMPILED && !NODE_SERVER_SCRIPT)");
});
test("Windows setup lane installs, doctors, and uninstalls rather than only building", () => {
const workflow = read(".github/workflows/windows-setup-e2e.yml");
expect(workflow).toContain("--dry-run --capabilities browser");
expect(workflow).toContain("--install-now --yes --capabilities browser");
expect(workflow).toContain("doctor --json");
expect(workflow).toContain("runtime/cli.js uninstall");
});
test("physical-iOS docs match the immutable five-iteration artifact", () => {
const artifact = JSON.parse(read("docs/gstack-2/evidence/ios-physical-device-2026-07-20T17-49-19-302Z.json"));
expect(artifact.passed).toBe(true);
expect(artifact.requiredIterations).toBe(5);
expect(artifact.passedIterations).toBe(5);
expect(artifact.iterations).toHaveLength(5);
expect(artifact.iterations.every((iteration: { passed: boolean }) => iteration.passed)).toBe(true);
for (const file of ["STATUS.md", "TEST-EVIDENCE.md", "ARCHITECTURE.md", "HOST-COMPATIBILITY.md", "IOS-PHYSICAL-DEVICE.md"]) {
expect(read(`docs/gstack-2/${file}`), file).toContain("ios-physical-device-2026-07-20T17-49-19-302Z.json");
}
});
test("public-tool decisions stay inside the accepted architecture", () => {
const adr = read("docs/gstack-2/adr/0001-public-infrastructure-tools.md");
expect(adr).toContain("Vercel Agent Skills CLI");
expect(adr).toContain("Sigstore Cosign");
expect(adr).toContain("No cloud-browser provider");
});
test("unavailable governance/static gates are explicit rather than claimed green", () => {
const policy = read("docs/gstack-2/RELEASE-INTEGRITY.md");
expect(policy).toContain("not claimed by the current six-artifact release matrix");
expect(policy).toContain("typecheck as not yet enforceable");
expect(policy).toContain("No `CODEOWNERS` file is invented");
expect(read(".github/workflows/quality-gate.yml")).toContain("gate-secret-scan.mjs");
});
});
+14
View File
@@ -94,6 +94,20 @@ describe('physical-device harness invariants', () => {
expect(classifyXcodebuildFailure('error: cannot find value in scope'))
.toBe('build_failed');
});
test('uses fresh HTTP connections for StateServer close responses', () => {
const harness = readFileSync(HARNESS_PATH, 'utf8');
expect(harness).toContain("connection: 'close'");
});
test('fixture exposes a public UIKit accessibility control to the in-process scanner', () => {
const app = readFileSync(
join(FIXTURE_PATH, 'Sources/FixtureApp/FixtureAppApp.swift'),
'utf8',
);
expect(app).toContain('struct FixtureButton: UIViewRepresentable');
expect(app).toContain('button.accessibilityIdentifier = "tap-button"');
});
});
describeIfDevice('ios physical-device path', () => {
test('Xcode/CoreDevice setup gates pass for one selected wired iPhone', () => {