gentle-pi 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -12
- package/extensions/gentle-ai.ts +313 -119
- package/lib/gentle-ai-binary.ts +64 -17
- package/lib/native-review-cli.ts +158 -12
- package/lib/review-candidate-view.ts +9 -5
- package/lib/review-integration-v2.ts +5 -1
- package/package.json +1 -1
- package/runtime/gentle-ai-binary.mjs +64 -17
- package/runtime/native-review-cli.mjs +158 -12
- package/runtime/review-integration-v2.mjs +5 -1
- package/scripts/gentle-ai-installer.mjs +454 -71
- package/scripts/test-packed-runner.mjs +1 -1
- package/skills/_shared/review-ledger-contract.md +3 -1
- package/tests/gentle-ai-binary.test.ts +70 -3
- package/tests/gentle-ai-installer.test.ts +543 -7
- package/tests/gentle-ai.test.ts +2 -2
- package/tests/native-review-consent.test.ts +128 -0
- package/tests/native-review-parity-runtime.test.ts +15 -6
- package/tests/native-review-parity.test.ts +155 -46
- package/tests/package-manifest.test.ts +10 -3
- package/tests/review-candidate-view.test.ts +43 -0
- package/tests/review-controller-native-routing.test.ts +247 -6
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-integration-v2.test.ts +10 -1
- package/tests/runtime-harness.mjs +24 -12
|
@@ -12,13 +12,13 @@ import {
|
|
|
12
12
|
resolveGentleAiBinary,
|
|
13
13
|
} from "../lib/gentle-ai-binary.ts";
|
|
14
14
|
import { NativeReviewCliV213, createNativeReviewCli, type ExecFileAdapter } from "../lib/native-review-cli.ts";
|
|
15
|
-
import { resolveGentleAiReleaseAsset } from "../scripts/gentle-ai-installer.mjs";
|
|
15
|
+
import { GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, resolveGentleAiReleaseAsset } from "../scripts/gentle-ai-installer.mjs";
|
|
16
16
|
import { requireNativeBinary } from "./support/native-binary-gate.ts";
|
|
17
17
|
|
|
18
18
|
const VERSION = { stdout: `gentle-ai ${GENTLE_AI_VERSION}\n`, stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false } as const;
|
|
19
19
|
const RUNTIME_DIRECTORY = `v${GENTLE_AI_VERSION}`;
|
|
20
20
|
const repoRuntimeBinary = join(import.meta.dirname, "..", ".gentle-ai", RUNTIME_DIRECTORY, process.platform === "win32" ? "gentle-ai.exe" : "gentle-ai");
|
|
21
|
-
const releaseDigestsPinned = /^[0-9a-f]{64}$/.test(resolveGentleAiReleaseAsset(process.platform, process.arch).sha256);
|
|
21
|
+
const releaseDigestsPinned = process.platform === "win32" || /^[0-9a-f]{64}$/.test(resolveGentleAiReleaseAsset(process.platform, process.arch).sha256);
|
|
22
22
|
// These integrity tests need the published official binary; they skip while a
|
|
23
23
|
// re-pinned release's archives and digest table are still pending.
|
|
24
24
|
const nativeBinaryGate = requireNativeBinary({
|
|
@@ -27,7 +27,7 @@ const nativeBinaryGate = requireNativeBinary({
|
|
|
27
27
|
env: process.env,
|
|
28
28
|
});
|
|
29
29
|
if (!nativeBinaryGate.run) console.log(`gentle-ai-binary: ${nativeBinaryGate.reason}`);
|
|
30
|
-
const verifiedBinaryTest = nativeBinaryGate.run ? test : test.skip;
|
|
30
|
+
const verifiedBinaryTest = nativeBinaryGate.run && process.platform !== "win32" ? test : test.skip;
|
|
31
31
|
|
|
32
32
|
async function writeVerifiedBinary(packageRoot: string, platform = process.platform): Promise<string> {
|
|
33
33
|
const asset = resolveGentleAiReleaseAsset(platform, process.arch);
|
|
@@ -39,6 +39,31 @@ async function writeVerifiedBinary(packageRoot: string, platform = process.platf
|
|
|
39
39
|
return binaryPath;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
async function writeWindowsSourceBinary(packageRoot: string): Promise<{ binaryPath: string; manifestPath: string }> {
|
|
43
|
+
const binaryPath = join(packageRoot, ".gentle-ai", RUNTIME_DIRECTORY, "gentle-ai.exe");
|
|
44
|
+
const manifestPath = join(packageRoot, ".gentle-ai", RUNTIME_DIRECTORY, "integrity.json");
|
|
45
|
+
const binary = "verified Windows source build";
|
|
46
|
+
await mkdir(join(packageRoot, ".gentle-ai", RUNTIME_DIRECTORY), { recursive: true });
|
|
47
|
+
await writeFile(binaryPath, binary);
|
|
48
|
+
await writeFile(manifestPath, `${JSON.stringify({
|
|
49
|
+
version: GENTLE_AI_VERSION,
|
|
50
|
+
method: "go-sumdb-source-build",
|
|
51
|
+
package: "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai",
|
|
52
|
+
module: "github.com/gentleman-programming/gentle-ai/v2",
|
|
53
|
+
tag: "v2.2.2",
|
|
54
|
+
architecture: process.arch === "x64" ? "x64" : "arm64",
|
|
55
|
+
binarySha256: createHash("sha256").update(binary).digest("hex"),
|
|
56
|
+
moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM,
|
|
57
|
+
goVersion: "go1.25.10",
|
|
58
|
+
goos: "windows",
|
|
59
|
+
goarch: process.arch === "x64" ? "amd64" : "arm64",
|
|
60
|
+
buildMode: "exe",
|
|
61
|
+
compiler: "gc",
|
|
62
|
+
cgoEnabled: "0",
|
|
63
|
+
})}\n`);
|
|
64
|
+
return { binaryPath, manifestPath };
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
verifiedBinaryTest("runtime resolves an absolute package-local binary path without PATH fallback", async () => {
|
|
43
68
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-binary-"));
|
|
44
69
|
const executable = process.platform === "win32" ? "gentle-ai.exe" : "gentle-ai";
|
|
@@ -51,6 +76,40 @@ verifiedBinaryTest("runtime resolves an absolute package-local binary path witho
|
|
|
51
76
|
assert.doesNotMatch(resolved, /(^|[/\\])PATH($|[/\\])/i);
|
|
52
77
|
});
|
|
53
78
|
|
|
79
|
+
test("runtime validates a Windows Go SumDB source manifest and rejects tampering, symlinks, and PATH injection", async () => {
|
|
80
|
+
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-windows-source-runtime-"));
|
|
81
|
+
const { binaryPath, manifestPath } = await writeWindowsSourceBinary(packageRoot);
|
|
82
|
+
assert.equal(resolveGentleAiBinary(packageRoot, "win32"), binaryPath);
|
|
83
|
+
|
|
84
|
+
const valid = JSON.parse(readFileSync(manifestPath, "utf8")) as Record<string, string>;
|
|
85
|
+
for (const manifest of [
|
|
86
|
+
{ ...valid, method: "signed-release-asset" },
|
|
87
|
+
{ ...valid, module: "example.invalid/gentle-ai" },
|
|
88
|
+
{ ...valid, tag: "v2.2.1" },
|
|
89
|
+
{ ...valid, binarySha256: "0".repeat(64) },
|
|
90
|
+
{ ...valid, moduleChecksum: "h1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" },
|
|
91
|
+
{ ...valid, goVersion: "go1.25.9" },
|
|
92
|
+
{ ...valid, goVersion: "go1.25" },
|
|
93
|
+
]) {
|
|
94
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest)}\n`);
|
|
95
|
+
assert.throws(() => resolveGentleAiBinary(packageRoot, "win32"), /package-local-binary-missing/);
|
|
96
|
+
}
|
|
97
|
+
await writeFile(manifestPath, `${JSON.stringify(valid)}\n`);
|
|
98
|
+
await writeFile(binaryPath, "tampered Windows source build");
|
|
99
|
+
assert.throws(() => resolveGentleAiBinary(packageRoot, "win32"), /package-local-binary-missing/);
|
|
100
|
+
await writeFile(binaryPath, "verified Windows source build");
|
|
101
|
+
await rm(binaryPath);
|
|
102
|
+
const ambient = join(packageRoot, "ambient-gentle-ai.exe");
|
|
103
|
+
await writeFile(ambient, "ambient executable");
|
|
104
|
+
await symlink(ambient, binaryPath);
|
|
105
|
+
assert.throws(() => resolveGentleAiBinary(packageRoot, "win32"), /package-local-binary-missing/);
|
|
106
|
+
assert.doesNotMatch(gentleAiBinaryPathForTest(packageRoot), /(^|[/\\])PATH($|[/\\])/i);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function gentleAiBinaryPathForTest(packageRoot: string): string {
|
|
110
|
+
return join(packageRoot, ".gentle-ai", RUNTIME_DIRECTORY, "gentle-ai.exe");
|
|
111
|
+
}
|
|
112
|
+
|
|
54
113
|
test("runtime rejects an unverified binary, a symlinked manifest, and ambient executable injection", async () => {
|
|
55
114
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-binary-integrity-"));
|
|
56
115
|
const executable = process.platform === "win32" ? "gentle-ai.exe" : "gentle-ai";
|
|
@@ -96,6 +155,14 @@ verifiedBinaryTest("runtime rejects malformed, unknown, wrong, and symlinked int
|
|
|
96
155
|
assert.throws(() => resolveGentleAiBinary(directoryRoot, process.platform), /package-local-binary-missing/);
|
|
97
156
|
});
|
|
98
157
|
|
|
158
|
+
verifiedBinaryTest("runtime rejects a binary-only tamper while its canonical pinned manifest remains unchanged", async () => {
|
|
159
|
+
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-binary-pinned-manifest-tamper-"));
|
|
160
|
+
const binaryPath = await writeVerifiedBinary(packageRoot);
|
|
161
|
+
await writeFile(binaryPath, "binary-only tamper");
|
|
162
|
+
if (process.platform !== "win32") await chmod(binaryPath, 0o700);
|
|
163
|
+
assert.throws(() => resolveGentleAiBinary(packageRoot, process.platform), /package-local-binary-missing/);
|
|
164
|
+
});
|
|
165
|
+
|
|
99
166
|
verifiedBinaryTest("runtime rejects an arbitrary binary even when a forged manifest matches its digest", async () => {
|
|
100
167
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-binary-forged-manifest-"));
|
|
101
168
|
const binaryPath = await writeVerifiedBinary(packageRoot);
|