gentle-pi 4.1.0 → 4.3.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 +352 -128
- package/assets/agents/gentle-ai-explore.md +1 -0
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -1
- package/assets/orchestrator-delegation-reference.md +163 -0
- package/assets/orchestrator-delegation.md +25 -176
- package/assets/orchestrator-tracking.md +1 -1
- package/assets/orchestrator-verification.md +14 -8
- package/assets/orchestrator-writer.md +5 -1
- package/assets/orchestrator.md +9 -9
- package/bin/engram-guard +1 -1
- package/bin/gentle-shell.mjs +188 -35
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
- package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/assets/diagrams/agent-orchestration.svg +54 -15
- package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
- package/docs/assets/diagrams/measured-routing.svg +49 -0
- package/docs/assets/diagrams/native-review.svg +52 -12
- package/docs/assets/diagrams/odd-workflow.svg +128 -66
- package/docs/assets/diagrams/permission-modes.svg +51 -0
- package/docs/assets/features/audio-notifications.png +0 -0
- package/docs/assets/features/command-palette.png +0 -0
- package/docs/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +40 -8
- package/docs/delegated-verification.md +10 -8
- package/docs/gentle-agents-activity.md +11 -5
- package/docs/gentle-shell-uninstall.md +8 -1
- package/docs/gentle-shell.md +20 -12
- package/docs/install-wizard.md +17 -10
- package/docs/permission-modes.md +51 -6
- package/docs/readme-reference.md +155 -31
- package/docs/session-profile-format.md +39 -0
- package/docs/yolo-mode.md +21 -7
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/codegraph-tools.ts +68 -6
- package/extensions/gentle-agents.ts +146 -28
- package/extensions/gentle-ai.ts +380 -68
- package/extensions/gentle-herdr-activity.ts +2 -3
- package/extensions/gentle-shell.ts +53 -43
- package/extensions/gentle-todo.ts +62 -18
- package/extensions/quiet-tools.ts +1 -1
- package/extensions/session-scratch.ts +365 -0
- package/extensions/startup-banner.ts +2 -3
- package/lib/agent-home.ts +8 -3
- package/lib/agents-protocol.ts +3 -0
- package/lib/agents-runner.ts +55 -6
- package/lib/agents-view.ts +3 -1
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +215 -36
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +251 -35
- package/lib/auto-permission-tool-context.ts +61 -0
- package/lib/bounded-writer-admission.ts +51 -0
- package/lib/command-palette-catalog.ts +0 -4
- package/lib/commit-slice-signal.ts +100 -0
- package/lib/context-backstop.ts +84 -0
- package/lib/delivered-test-lint.ts +80 -0
- package/lib/destructive-command-guard.ts +215 -28
- package/lib/gauge-theme.ts +146 -0
- package/lib/gentle-ai-binary.ts +28 -7
- package/lib/gentle-shell-bundled.ts +62 -8
- package/lib/gentle-shell-import-pi.ts +233 -0
- package/lib/gentle-shell-launcher.ts +146 -12
- package/lib/gentle-shell-uninstall.ts +18 -1
- package/lib/herdr-activity.ts +24 -0
- package/lib/model-routing-authority.ts +6 -1
- package/lib/native-review-cli.ts +9 -1
- package/lib/orchestrator-discovery.ts +5 -1
- package/lib/orchestrator-presence.ts +9 -0
- package/lib/orchestrator-scope.ts +21 -3
- package/lib/provider-contract-bundle.ts +99 -53
- package/lib/review-candidate-view-owner.ts +29 -19
- package/lib/review-candidate-view.ts +36 -2
- package/lib/review-risk-assessment.ts +43 -38
- package/lib/review-session-standing-permission.ts +3 -1
- package/lib/selection-engine.ts +4 -1
- package/lib/session-profile-freeze.ts +40 -16
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-scratch.ts +474 -0
- package/lib/shell-bar.ts +25 -25
- package/lib/shell-changes-view.ts +80 -13
- package/lib/shell-changes.ts +2 -0
- package/lib/shell-gauge.ts +49 -6
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-todo.ts +39 -8
- package/lib/shell-usage-view.ts +5 -1
- package/lib/shell-usage.ts +7 -7
- package/lib/verifier-admission.ts +29 -0
- package/lib/vim-editor-adapter.ts +22 -4
- package/lib/yolo-temp-scratch.ts +69 -0
- package/package.json +5 -6
- package/runtime/gentle-ai-binary.mjs +28 -7
- package/runtime/gentle-shell-bundled.mjs +61 -7
- package/runtime/gentle-shell-import-pi.mjs +234 -0
- package/runtime/gentle-shell-launcher.mjs +144 -10
- package/runtime/gentle-shell-uninstall.mjs +18 -1
- package/runtime/native-review-cli.mjs +8 -0
- package/runtime/review-risk-assessment.mjs +43 -38
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/bundled-engram-migration.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-wizard-preview.mjs +2 -2
- package/scripts/installer-downloads.mjs +7 -7
- package/scripts/installer-preflight.mjs +1 -1
- package/scripts/installer-server.mjs +14 -4
- package/scripts/main-channel.mjs +3 -2
- package/scripts/pi-update.mjs +223 -0
- package/scripts/run-test-suite.mjs +5 -0
- package/scripts/verify-package-files.mjs +19 -13
- package/skills/gentle-ai/SKILL.md +3 -3
- package/skills/work-unit-commits/SKILL.md +5 -3
- package/tests/agent-risk-escalation.test.ts +37 -10
- package/tests/agents-runner.test.ts +260 -3
- package/tests/append-system-prompt-route.test.ts +18 -1
- package/tests/auto-mode-human-answers.test.ts +68 -0
- package/tests/auto-mode-invariants.test.ts +23 -5
- package/tests/auto-mode-sdk.test.ts +29 -2
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +353 -2
- package/tests/auto-permission-compound.test.ts +24 -0
- package/tests/auto-permission-evidence.test.ts +88 -0
- package/tests/auto-permission-memory.test.ts +46 -0
- package/tests/auto-permission-public-regressions.test.ts +111 -0
- package/tests/auto-permission-runtime.test.ts +270 -12
- package/tests/autonomous-guard.test.ts +208 -16
- package/tests/bounded-writer-admission.test.ts +11 -3
- package/tests/bundled-install.test.ts +3 -3
- package/tests/card-content-policy.test.ts +19 -9
- package/tests/child-safety.test.ts +39 -0
- package/tests/codegraph-tools.test.ts +123 -2
- package/tests/command-palette.test.ts +14 -4
- package/tests/commit-slice-signal.test.ts +162 -0
- package/tests/context-backstop.test.ts +159 -0
- package/tests/delegate-for-reason-contract.test.ts +91 -24
- package/tests/delivered-test-lint.test.ts +105 -0
- package/tests/destructive-command-guard.test.ts +85 -1
- package/tests/engram-safety.test.ts +35 -7
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
- package/tests/gauge-theme.test.ts +120 -0
- package/tests/gentle-agents.test.ts +443 -8
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-child-guards.test.ts +61 -0
- package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
- package/tests/gentle-ai-installer.test.ts +51 -51
- package/tests/gentle-shell-bin.test.ts +180 -17
- package/tests/gentle-shell-bundled.test.ts +235 -38
- package/tests/gentle-shell-import-pi.test.ts +262 -0
- package/tests/gentle-shell-launcher.test.ts +97 -13
- package/tests/gentle-shell-uninstall.test.ts +30 -0
- package/tests/gentle-shell.test.ts +113 -14
- package/tests/gentle-todo.test.ts +258 -27
- package/tests/headless-delivery-question.test.ts +58 -0
- package/tests/herdr-activity.test.ts +34 -1
- package/tests/install-wizard.test.ts +2 -2
- package/tests/installer-posix-bootstrap.test.ts +8 -8
- package/tests/installer-preflight.test.ts +3 -3
- package/tests/installer-probes.test.ts +8 -8
- package/tests/installer-runner.test.ts +14 -13
- package/tests/installer-server.test.ts +13 -5
- package/tests/installer-windows-bootstrap.test.ts +33 -4
- package/tests/main-channel-upgrade.test.ts +7 -1
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-parity.test.ts +22 -1
- package/tests/no-self-pause-contract.test.ts +5 -2
- package/tests/odd-integration.test.ts +5 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +127 -162
- package/tests/odd-routing-contract.test.ts +115 -30
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +19 -8
- package/tests/orchestrator-discovery.test.ts +29 -0
- package/tests/orchestrator-rdd-ownership.test.ts +3 -1
- package/tests/orchestrator-scope.test.ts +66 -0
- package/tests/orchestrator-status-publication-contract.test.ts +12 -8
- package/tests/package-manifest.test.ts +28 -24
- package/tests/pi-update-launcher.test.ts +106 -0
- package/tests/pi-update.test.ts +186 -0
- package/tests/primary-session-prompt.test.ts +101 -0
- package/tests/provider-contract-bundle.test.ts +118 -0
- package/tests/provider-contract-mirror.test.ts +24 -0
- package/tests/quiet-tool-rendering.test.ts +23 -0
- package/tests/rdd-aware-verification-contract.test.ts +29 -11
- package/tests/rdd-status-line.test.ts +131 -0
- package/tests/review-agent-end-preflight.test.ts +6 -0
- package/tests/review-candidate-view.test.ts +49 -0
- package/tests/review-contract-prompt.test.ts +5 -2
- package/tests/review-controller-native-routing.test.ts +14 -1
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +59 -2
- package/tests/review-loadout-rdd-off-sdk.test.ts +237 -0
- package/tests/review-owner-identity.test.ts +132 -0
- package/tests/review-risk-assessment.test.ts +163 -76
- package/tests/review-session-standing-permission-controller.test.ts +14 -1
- package/tests/review-session-standing-permission.test.ts +27 -1
- package/tests/run-test-suite.test.ts +15 -0
- package/tests/runtime-harness.mjs +16 -6
- package/tests/selection-engine.test.ts +35 -5
- package/tests/session-changes-shell.test.ts +1 -1
- package/tests/session-profile-freeze.test.ts +37 -1
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-scratch.test.ts +945 -0
- package/tests/session-subject-routing-contract.test.ts +2 -1
- package/tests/shell-bar.test.ts +7 -0
- package/tests/shell-changes-view.test.ts +137 -1
- package/tests/shell-changes.test.ts +18 -0
- package/tests/shell-gauge.test.ts +42 -0
- package/tests/shell-todo.test.ts +15 -12
- package/tests/small-task-inline-contract.test.ts +37 -0
- package/tests/standalone-config-home.test.ts +324 -0
- package/tests/startup-banner.test.ts +5 -4
- package/tests/startup-home-repo.test.ts +34 -0
- package/tests/support/orchestrator-modules.ts +1 -0
- package/tests/task-size-routing-contract.test.ts +69 -9
- package/tests/test-scratch-contract.test.ts +102 -0
- package/tests/test-strength-contract.test.ts +36 -1
- package/tests/tool-batching-contract.test.ts +64 -0
- package/tests/verification-proportionality.test.ts +46 -0
- package/tests/verifier-admission.test.ts +137 -0
- package/tests/verify-agent-contract.test.ts +8 -8
- package/tests/vim-editor-adapter.test.ts +41 -15
- package/tests/windows-hidden-processes.test.ts +5 -0
- package/tests/writer-edit-surface-scope.test.ts +3 -0
- package/tests/writer-feature-document-admission.test.ts +156 -0
- package/tests/yolo-mode.test.ts +24 -1
- package/tests/yolo-temp-scratch.test.ts +71 -0
- package/scripts/mirror-odd-routing.mjs +0 -242
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
|
+
import { createServer, type Server } from "node:net";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import test, { afterEach, beforeEach } from "node:test";
|
|
7
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
9
|
+
import { setGentleAiDevBinaryEnvironmentForTesting } from "../lib/gentle-ai-binary.ts";
|
|
10
|
+
|
|
11
|
+
// /gentle:doctor surfaces the Herdr sidebar setup only inside Herdr. It is
|
|
12
|
+
// read-only guidance: Herdr config is never written.
|
|
13
|
+
|
|
14
|
+
const ISOLATED = ["GENTLE_PI_CONFIG_HOME", "GENTLE_PI_AGENT_HOME", "GENTLE_PI_AGENTS_CHILD", "HERDR_ENV", "HERDR_PANE_ID", "HERDR_SOCKET_PATH"];
|
|
15
|
+
let saved: Record<string, string | undefined> = {};
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
saved = Object.fromEntries(ISOLATED.map((name) => [name, process.env[name]]));
|
|
18
|
+
for (const name of ISOLATED) delete process.env[name];
|
|
19
|
+
process.env.GENTLE_PI_CONFIG_HOME = await mkdtemp(join(tmpdir(), "gentle-pi-doctor-config-"));
|
|
20
|
+
process.env.GENTLE_PI_AGENT_HOME = await mkdtemp(join(tmpdir(), "gentle-pi-doctor-agent-"));
|
|
21
|
+
setGentleAiDevBinaryEnvironmentForTesting({ env: {}, home: await mkdtemp(join(tmpdir(), "gentle-pi-doctor-home-")) });
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
setGentleAiDevBinaryEnvironmentForTesting(undefined);
|
|
25
|
+
for (const name of ISOLATED) {
|
|
26
|
+
if (saved[name] === undefined) delete process.env[name];
|
|
27
|
+
else process.env[name] = saved[name];
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
async function runDoctor(): Promise<Array<{ message: string; severity: string }>> {
|
|
32
|
+
const commands = new Map<string, { handler: (args: string, ctx: ExtensionContext) => Promise<void> }>();
|
|
33
|
+
const pi = {
|
|
34
|
+
on() {},
|
|
35
|
+
registerCommand(name: string, registration: { handler: (args: string, ctx: ExtensionContext) => Promise<void> }) {
|
|
36
|
+
commands.set(name, registration);
|
|
37
|
+
},
|
|
38
|
+
registerTool() {},
|
|
39
|
+
} as unknown as ExtensionAPI;
|
|
40
|
+
createGentleAiExtension({ nativeReviewCli: null })(pi);
|
|
41
|
+
const notifications: Array<{ message: string; severity: string }> = [];
|
|
42
|
+
const ctx = {
|
|
43
|
+
cwd: await mkdtemp(join(tmpdir(), "gentle-pi-doctor-cwd-")),
|
|
44
|
+
mode: "tui",
|
|
45
|
+
hasUI: true,
|
|
46
|
+
ui: { notify(message: string, severity: string) { notifications.push({ message, severity }); } },
|
|
47
|
+
} as unknown as ExtensionContext;
|
|
48
|
+
await commands.get("gentle:doctor")!.handler("", ctx);
|
|
49
|
+
return notifications;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function listen(path: string): Promise<Server> {
|
|
53
|
+
const server = createServer();
|
|
54
|
+
await new Promise<void>((resolve) => server.listen(path, resolve));
|
|
55
|
+
return server;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
test("outside Herdr, /gentle:doctor stays silent about Herdr", async () => {
|
|
59
|
+
const notifications = await runDoctor();
|
|
60
|
+
assert.equal(notifications.length, 1);
|
|
61
|
+
assert.match(notifications[0]!.message, /^el Gentleman doctor\n/);
|
|
62
|
+
assert.doesNotMatch(notifications[0]!.message, /herdr|\$summary/i);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("inside Herdr, /gentle:doctor appends the read-only sidebar setup hint", async () => {
|
|
66
|
+
const baseline = await runDoctor();
|
|
67
|
+
const socket = join(await mkdtemp(join(tmpdir(), "gd-")), "h.sock");
|
|
68
|
+
const server = await listen(socket);
|
|
69
|
+
try {
|
|
70
|
+
Object.assign(process.env, { HERDR_ENV: "1", HERDR_PANE_ID: "pane-1", HERDR_SOCKET_PATH: socket });
|
|
71
|
+
const notifications = await runDoctor();
|
|
72
|
+
assert.equal(notifications.length, 1);
|
|
73
|
+
assert.equal(notifications[0]!.severity, baseline[0]!.severity, "the hint does not change doctor severity");
|
|
74
|
+
assert.ok(notifications[0]!.message.endsWith([
|
|
75
|
+
"Herdr",
|
|
76
|
+
"pass: Herdr task-summary publishing active",
|
|
77
|
+
"info: Herdr's default sidebar does not show task titles; add these two rows to the Agents sidebar rows in your Herdr config: ['$summary'], ['$summary2']",
|
|
78
|
+
"info: Sidebar rows belong to the config of the Herdr client that displays the session, including when Pi runs over SSH",
|
|
79
|
+
"info: After editing, reload or restart Herdr; Gentle Shell never changes Herdr config",
|
|
80
|
+
"info: Details: https://github.com/Gentleman-Programming/gentle-shell/blob/main/docs/readme-reference.md#herdr-active-work-summary",
|
|
81
|
+
].join("\n")), notifications[0]!.message);
|
|
82
|
+
assert.ok(notifications[0]!.message.startsWith(baseline[0]!.message), "existing doctor lines are unchanged");
|
|
83
|
+
} finally {
|
|
84
|
+
await new Promise((resolve) => server.close(resolve));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("inside Herdr without a live socket, /gentle:doctor reports publishing inactive", async () => {
|
|
89
|
+
Object.assign(process.env, { HERDR_ENV: "1", HERDR_PANE_ID: "pane-1", HERDR_SOCKET_PATH: join(tmpdir(), "missing-herdr.sock") });
|
|
90
|
+
const notifications = await runDoctor();
|
|
91
|
+
assert.equal(notifications.length, 1);
|
|
92
|
+
assert.match(notifications[0]!.message, /\nwarn: Herdr task-summary publishing inactive: HERDR_SOCKET_PATH is not a Unix socket\n/);
|
|
93
|
+
assert.match(notifications[0]!.message, /\['\$summary'\], \['\$summary2'\]/);
|
|
94
|
+
});
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
trustedSystemExtractor,
|
|
25
25
|
} from "../scripts/gentle-ai-installer.mjs";
|
|
26
26
|
|
|
27
|
-
// v4.
|
|
27
|
+
// v4.2.0 archive digests independently match the minisign-signed release
|
|
28
28
|
// checksums; binary digests were computed from the extracted executables.
|
|
29
29
|
function releaseAssets(): Record<string, { name: string; sha256: string; binarySha256: string; url: string }> {
|
|
30
30
|
const assets: Record<string, { name: string; sha256: string; binarySha256: string; url: string }> = {};
|
|
@@ -40,17 +40,17 @@ function releaseAssets(): Record<string, { name: string; sha256: string; binaryS
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const EXPECTED_ASSETS = {
|
|
43
|
-
"darwin/amd64": { name: "gentle-ai_4.
|
|
44
|
-
"darwin/arm64": { name: "gentle-ai_4.
|
|
45
|
-
"linux/amd64": { name: "gentle-ai_4.
|
|
46
|
-
"linux/arm64": { name: "gentle-ai_4.
|
|
43
|
+
"darwin/amd64": { name: "gentle-ai_4.2.0_darwin_amd64.tar.gz", sha256: "8f6a91e0f99cac3aea138705d7d222ea342f53a47602d48cb01488952a1c12f8", binarySha256: "784855e99f1bc45579f01420c923b7dc1dc6d63492cdc0174a9a32e611849e57" },
|
|
44
|
+
"darwin/arm64": { name: "gentle-ai_4.2.0_darwin_arm64.tar.gz", sha256: "d5e9a6521de5f08a393635f083f64dc7afc1b8e97884f7aab5ae5eb1f17c7f37", binarySha256: "8ea490287ef796848b0d305cf2b26265e360d3f400efc016972844498fb87097" },
|
|
45
|
+
"linux/amd64": { name: "gentle-ai_4.2.0_linux_amd64.tar.gz", sha256: "552c1642fd10f5d96cfe9bb1cc3b66b30eb47d3074cc7ca0559b598eb086defc", binarySha256: "9212c7b16ea9abfe93e3a16b29d8390e7762b5c07730cf05df1f3e7892ff96a4" },
|
|
46
|
+
"linux/arm64": { name: "gentle-ai_4.2.0_linux_arm64.tar.gz", sha256: "2bebe91b8808ce48d86c4d3700d098e37cb6b0658f196c666d9deeeb7c7c1a7a", binarySha256: "e1b88bd3dcdc8a15ddbcaf8731fd44561f05b06fbcb59d9385bbe82c4b96b5c4" },
|
|
47
47
|
} as const;
|
|
48
48
|
|
|
49
|
-
test("published Gentle AI v4.
|
|
50
|
-
assert.equal(INSTALLER_VERSION, "4.
|
|
49
|
+
test("published Gentle AI v4.2.0 is the installer pin", () => {
|
|
50
|
+
assert.equal(INSTALLER_VERSION, "4.2.0");
|
|
51
51
|
// v4.0.0 moved the Go module path to the /v4 major-version suffix.
|
|
52
52
|
assert.equal(GENTLE_AI_WINDOWS_SOURCE_MODULE, "github.com/gentleman-programming/gentle-ai/v4");
|
|
53
|
-
assert.equal(GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, "h1
|
|
53
|
+
assert.equal(GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, "h1:J8EluS7QrfM+m3vmQlF26B/shZRjD6rBfsKtrABL5mc=");
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
test("default installer package root is the package containing scripts, not its parent", () => {
|
|
@@ -61,15 +61,15 @@ test("default installer package root is the package containing scripts, not its
|
|
|
61
61
|
assert.notEqual(resolveGentleAiInstallerPackageRoot(), dirname(expectedPackageRoot));
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
test("release mapping selects only the supported official v4.
|
|
64
|
+
test("release mapping selects only the supported official v4.2.0 assets and pinned digests", () => {
|
|
65
65
|
assert.deepEqual(
|
|
66
66
|
Object.fromEntries(Object.entries(releaseAssets()).map(([key, asset]) => [key, { name: asset.name, sha256: asset.sha256, binarySha256: asset.binarySha256 }])),
|
|
67
67
|
EXPECTED_ASSETS,
|
|
68
68
|
);
|
|
69
|
-
assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_4.
|
|
70
|
-
assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_4.
|
|
69
|
+
assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_4.2.0_linux_amd64.tar.gz");
|
|
70
|
+
assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_4.2.0_darwin_arm64.tar.gz");
|
|
71
71
|
for (const asset of Object.values(releaseAssets())) {
|
|
72
|
-
assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v4\.
|
|
72
|
+
assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v4\.2\.0\//);
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
75
|
|
|
@@ -79,7 +79,7 @@ test("raw release assets are admitted only under a prerelease pin", () => {
|
|
|
79
79
|
assert.equal(gentleAiAssetForm("gentle-ai_2.5.0-rc.3_windows_amd64.exe", "2.5.0-rc.3"), "raw-binary");
|
|
80
80
|
// A raw binary under a stable pin means the pin itself is wrong: stable
|
|
81
81
|
// releases publish signed archives only, so this fails closed pre-download.
|
|
82
|
-
assert.throws(() => gentleAiAssetForm("gentle-ai_4.
|
|
82
|
+
assert.throws(() => gentleAiAssetForm("gentle-ai_4.2.0_linux_amd64", "4.2.0"), /only admitted for a prerelease pin/);
|
|
83
83
|
assert.throws(() => gentleAiAssetForm("gentle-ai.dmg", "2.5.0-rc.3"), /unsupported Gentle AI release asset form/);
|
|
84
84
|
// The current stable pin admits every pinned asset row through the same
|
|
85
85
|
// gate the installer uses at download time (default installerVersion
|
|
@@ -106,7 +106,7 @@ test("release digests are all-or-none and install fails closed while any digest
|
|
|
106
106
|
}),
|
|
107
107
|
/checksum mismatch/,
|
|
108
108
|
);
|
|
109
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.
|
|
109
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
112
|
|
|
@@ -136,7 +136,7 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) {
|
|
|
136
136
|
const metadata = [
|
|
137
137
|
"gentle-ai.exe: go1.25.10",
|
|
138
138
|
"\tpath\tgithub.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai",
|
|
139
|
-
`\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.
|
|
139
|
+
`\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.2.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
|
|
140
140
|
"\tbuild\t-buildmode=exe", "\tbuild\t-compiler=gc", "\tbuild\tCGO_ENABLED=0", `\tbuild\tGOARCH=${fixtureOptions.goArchitecture ?? "amd64"}`, "\tbuild\tGOOS=windows",
|
|
141
141
|
].join("\n");
|
|
142
142
|
const run = async (file: string, arguments_: string[], options: WindowsGoCall["options"]) => {
|
|
@@ -154,7 +154,7 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) {
|
|
|
154
154
|
return { stdout: "", stderr: "" };
|
|
155
155
|
}
|
|
156
156
|
if (file === goExecutable && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" };
|
|
157
|
-
if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 4.
|
|
157
|
+
if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 4.2.0\n", stderr: "" };
|
|
158
158
|
throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`);
|
|
159
159
|
};
|
|
160
160
|
return { calls, run, setGoExecutable: (path: string) => { goExecutable = path; } };
|
|
@@ -170,7 +170,7 @@ test("win32 x64 and arm64 install the exact Go SumDB source tag without archive
|
|
|
170
170
|
const result = await installGentleAi({ packageRoot, platform: "win32", arch, execFile: fixture.run, resolveGoExecutable: async () => goPath });
|
|
171
171
|
assert.equal(result.installed, true);
|
|
172
172
|
assert.deepEqual(fixture.calls.filter((call) => call.file === goPath).map((call) => call.arguments_.slice(0, 2)), [
|
|
173
|
-
["version"], ["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.
|
|
173
|
+
["version"], ["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.2.0"], ["version", "-m"],
|
|
174
174
|
]);
|
|
175
175
|
}
|
|
176
176
|
});
|
|
@@ -189,7 +189,7 @@ test("Windows source install reports missing or too-old Go without publishing a
|
|
|
189
189
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }),
|
|
190
190
|
(error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode,
|
|
191
191
|
);
|
|
192
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.
|
|
192
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai.exe")), false);
|
|
193
193
|
assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.includes("install-")), []);
|
|
194
194
|
}
|
|
195
195
|
});
|
|
@@ -235,7 +235,7 @@ test("Windows source install builds in a short private directory under the temp
|
|
|
235
235
|
}
|
|
236
236
|
for (const call of fixture.calls.filter((entry) => entry.file === goPath)) assert.ok(call.options.cwd?.startsWith(buildDirectory), call.arguments_.join(" "));
|
|
237
237
|
assert.deepEqual(await readdir(temporaryDirectory), [], "the build directory is removed");
|
|
238
|
-
if (!installError) assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.
|
|
238
|
+
if (!installError) assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai.exe")), true, "the verified binary is published into the package");
|
|
239
239
|
} finally {
|
|
240
240
|
await rm(packageRoot, { recursive: true, force: true });
|
|
241
241
|
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
@@ -257,7 +257,7 @@ test("Windows source install cleans staging after Go failure or wrong built vers
|
|
|
257
257
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }),
|
|
258
258
|
(error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode,
|
|
259
259
|
);
|
|
260
|
-
const runtimeDirectory = join(packageRoot, ".gentle-ai", "v4.
|
|
260
|
+
const runtimeDirectory = join(packageRoot, ".gentle-ai", "v4.2.0");
|
|
261
261
|
assert.ok(fixture.calls.some((call) => call.arguments_[0] === "install"));
|
|
262
262
|
assert.equal(existsSync(join(runtimeDirectory, "gentle-ai.exe")), false);
|
|
263
263
|
assert.equal(existsSync(runtimeDirectory) && (await readdir(runtimeDirectory)).some((entry) => entry.startsWith(".go-install-") || entry.endsWith(".tmp")), false);
|
|
@@ -278,7 +278,7 @@ test("Windows source installs reuse only a fully verified package-local binary",
|
|
|
278
278
|
assert.equal(reused.installed, false);
|
|
279
279
|
assert.ok(reuse.calls.every((call) => call.file !== "gentle-ai"), "the installer must never fall back to ambient gentle-ai on PATH");
|
|
280
280
|
|
|
281
|
-
await writeFile(join(packageRoot, ".gentle-ai", "v4.
|
|
281
|
+
await writeFile(join(packageRoot, ".gentle-ai", "v4.2.0", "integrity.json"), "{}\n");
|
|
282
282
|
const repaired = windowsGoFixture();
|
|
283
283
|
repaired.setGoExecutable(goPath);
|
|
284
284
|
assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: repaired.run, resolveGoExecutable: async () => goPath })).installed, true);
|
|
@@ -320,7 +320,7 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har
|
|
|
320
320
|
const metadata = fixtureOptions.metadataOverride ?? [
|
|
321
321
|
"gentle-ai.exe: go1.25.10",
|
|
322
322
|
"\tpath\tgithub.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai",
|
|
323
|
-
`\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.
|
|
323
|
+
`\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.2.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
|
|
324
324
|
"\tbuild\t-buildmode=exe",
|
|
325
325
|
"\tbuild\t-compiler=gc",
|
|
326
326
|
"\tbuild\tCGO_ENABLED=0",
|
|
@@ -340,7 +340,7 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har
|
|
|
340
340
|
return { stdout: "", stderr: "" };
|
|
341
341
|
}
|
|
342
342
|
if (file === goPath && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" };
|
|
343
|
-
if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 4.
|
|
343
|
+
if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 4.2.0\n", stderr: "" };
|
|
344
344
|
throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`);
|
|
345
345
|
};
|
|
346
346
|
return {
|
|
@@ -382,7 +382,7 @@ test("Windows source installation resolves one validated Go executable and seals
|
|
|
382
382
|
const goCalls = fixture.calls.filter((call) => call.file === fixture.goPath);
|
|
383
383
|
assert.deepEqual(goCalls.map((call) => call.arguments_.slice(0, 2)), [
|
|
384
384
|
["version"],
|
|
385
|
-
["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.
|
|
385
|
+
["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.2.0"],
|
|
386
386
|
["version", "-m"],
|
|
387
387
|
]);
|
|
388
388
|
for (const call of goCalls) {
|
|
@@ -408,14 +408,14 @@ test("Windows source manifest binds verified Go metadata and architecture", asyn
|
|
|
408
408
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-provenance-"));
|
|
409
409
|
const fixture = await hardenedWindowsGoFixture(packageRoot, { architecture: "arm64" });
|
|
410
410
|
const result = await installGentleAi({ packageRoot, platform: "win32", arch: "arm64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
|
|
411
|
-
const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v4.
|
|
411
|
+
const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v4.2.0", "integrity.json"), "utf8")) as Record<string, string>;
|
|
412
412
|
assert.equal(result.installed, true);
|
|
413
413
|
assert.deepEqual(manifest, {
|
|
414
|
-
version: "4.
|
|
414
|
+
version: "4.2.0",
|
|
415
415
|
method: "go-sumdb-source-build",
|
|
416
416
|
package: "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai",
|
|
417
417
|
module: "github.com/gentleman-programming/gentle-ai/v4",
|
|
418
|
-
tag: "v4.
|
|
418
|
+
tag: "v4.2.0",
|
|
419
419
|
architecture: "arm64",
|
|
420
420
|
binarySha256: createHash("sha256").update("trusted Windows source build").digest("hex"),
|
|
421
421
|
moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM,
|
|
@@ -435,12 +435,12 @@ test("Windows source installation rejects Go metadata for a different architectu
|
|
|
435
435
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }),
|
|
436
436
|
(error: unknown) => error instanceof Error && "code" in error && error.code === "GENTLE_AI_GO_INSTALL_FAILED",
|
|
437
437
|
);
|
|
438
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.
|
|
438
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai.exe")), false);
|
|
439
439
|
});
|
|
440
440
|
|
|
441
441
|
test("Windows source installation treats a fresh ownerless lock as active", async () => {
|
|
442
442
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-lock-"));
|
|
443
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v4.
|
|
443
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
|
|
444
444
|
await mkdir(lockPath, { recursive: true });
|
|
445
445
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
446
446
|
await assertManualLockRecoveryRequired(packageRoot, fixture);
|
|
@@ -448,7 +448,7 @@ test("Windows source installation treats a fresh ownerless lock as active", asyn
|
|
|
448
448
|
|
|
449
449
|
test("Windows source installation preserves a lock whose owner nonce changed before release", async () => {
|
|
450
450
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owner-lock-"));
|
|
451
|
-
const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v4.
|
|
451
|
+
const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock", "owner.json");
|
|
452
452
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
453
453
|
let replacedOwner = false;
|
|
454
454
|
const run = async (...arguments_: Parameters<typeof fixture.run>) => {
|
|
@@ -465,7 +465,7 @@ test("Windows source installation preserves a lock whose owner nonce changed bef
|
|
|
465
465
|
});
|
|
466
466
|
|
|
467
467
|
async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Awaited<ReturnType<typeof hardenedWindowsGoFixture>>, options: Record<string, unknown> = {}) {
|
|
468
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v4.
|
|
468
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
|
|
469
469
|
await assert.rejects(
|
|
470
470
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable, ...options }),
|
|
471
471
|
(error: unknown) => error instanceof Error && error.message.includes(lockPath) && /confirm no installer is active.*remove.*manually/i.test(error.message),
|
|
@@ -475,18 +475,18 @@ async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Aw
|
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
function tombstonePath(packageRoot: string, nonce: string): string {
|
|
478
|
-
return join(packageRoot, ".gentle-ai", `.v4.
|
|
478
|
+
return join(packageRoot, ".gentle-ai", `.v4.2.0.install.tombstone-${nonce}`);
|
|
479
479
|
}
|
|
480
480
|
|
|
481
481
|
async function tombstones(packageRoot: string): Promise<string[]> {
|
|
482
482
|
const runtimeRoot = join(packageRoot, ".gentle-ai");
|
|
483
483
|
return existsSync(runtimeRoot)
|
|
484
|
-
? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v4.
|
|
484
|
+
? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v4.2.0.install.tombstone-"))
|
|
485
485
|
: [];
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
function backupBundlePath(packageRoot: string, nonce: string): string {
|
|
489
|
-
return join(packageRoot, ".gentle-ai", `.v4.
|
|
489
|
+
return join(packageRoot, ".gentle-ai", `.v4.2.0.backup-${nonce}`);
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
async function copyWindowsBundle(source: string, destination: string): Promise<void> {
|
|
@@ -497,7 +497,7 @@ async function copyWindowsBundle(source: string, destination: string): Promise<v
|
|
|
497
497
|
|
|
498
498
|
test("Windows source installation fails closed for an ownerless lock even after the stale threshold", async () => {
|
|
499
499
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-stale-lock-"));
|
|
500
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v4.
|
|
500
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
|
|
501
501
|
await mkdir(lockPath, { recursive: true });
|
|
502
502
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
503
503
|
await assertManualLockRecoveryRequired(packageRoot, fixture, { now: () => Date.now() + 10 * 60_000 });
|
|
@@ -505,7 +505,7 @@ test("Windows source installation fails closed for an ownerless lock even after
|
|
|
505
505
|
|
|
506
506
|
test("Windows source installation fails closed for a stale owner lock", async () => {
|
|
507
507
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-stale-lock-"));
|
|
508
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v4.
|
|
508
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
|
|
509
509
|
await mkdir(lockPath, { recursive: true });
|
|
510
510
|
await writeFile(join(lockPath, "owner.json"), `${JSON.stringify({ createdAt: 0, nonce: "0".repeat(64) })}\n`);
|
|
511
511
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
@@ -557,7 +557,7 @@ test("Windows source acquisition fails closed when a tombstone appears after its
|
|
|
557
557
|
(error: unknown) => error instanceof Error && error.message.includes(foreignTombstone),
|
|
558
558
|
);
|
|
559
559
|
assert.equal(existsSync(foreignTombstone), true);
|
|
560
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.
|
|
560
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock")), false);
|
|
561
561
|
assert.equal(fixture.calls.some((call) => call.arguments_[0] === "install"), false);
|
|
562
562
|
});
|
|
563
563
|
|
|
@@ -565,7 +565,7 @@ test("Windows source release deletes its matching tombstone and allows reuse", a
|
|
|
565
565
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owned-lock-release-"));
|
|
566
566
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
567
567
|
await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
|
|
568
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.
|
|
568
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock")), false);
|
|
569
569
|
assert.deepEqual(await tombstones(packageRoot), []);
|
|
570
570
|
const reuse = await hardenedWindowsGoFixture(packageRoot);
|
|
571
571
|
assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: reuse.run, resolveGoExecutable: reuse.resolveGoExecutable })).installed, false);
|
|
@@ -575,7 +575,7 @@ test("Windows source recovers a valid backup after a crash between publication r
|
|
|
575
575
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-crash-"));
|
|
576
576
|
const initial = await hardenedWindowsGoFixture(packageRoot);
|
|
577
577
|
await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
|
|
578
|
-
const live = join(packageRoot, ".gentle-ai", "v4.
|
|
578
|
+
const live = join(packageRoot, ".gentle-ai", "v4.2.0");
|
|
579
579
|
const backup = backupBundlePath(packageRoot, "crash");
|
|
580
580
|
await rename(live, backup);
|
|
581
581
|
const recovery = await hardenedWindowsGoFixture(packageRoot);
|
|
@@ -611,7 +611,7 @@ test("Windows source cleans one validated backup only when the live bundle is va
|
|
|
611
611
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-valid-live-"));
|
|
612
612
|
const initial = await hardenedWindowsGoFixture(packageRoot);
|
|
613
613
|
await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
|
|
614
|
-
const live = join(packageRoot, ".gentle-ai", "v4.
|
|
614
|
+
const live = join(packageRoot, ".gentle-ai", "v4.2.0");
|
|
615
615
|
const backup = backupBundlePath(packageRoot, "valid");
|
|
616
616
|
await copyWindowsBundle(live, backup);
|
|
617
617
|
const reuse = await hardenedWindowsGoFixture(packageRoot);
|
|
@@ -624,7 +624,7 @@ test("Windows source preserves a valid backup when the live bundle is invalid",
|
|
|
624
624
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-invalid-live-"));
|
|
625
625
|
const initial = await hardenedWindowsGoFixture(packageRoot);
|
|
626
626
|
await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
|
|
627
|
-
const live = join(packageRoot, ".gentle-ai", "v4.
|
|
627
|
+
const live = join(packageRoot, ".gentle-ai", "v4.2.0");
|
|
628
628
|
const backup = backupBundlePath(packageRoot, "valid");
|
|
629
629
|
await copyWindowsBundle(live, backup);
|
|
630
630
|
await writeFile(join(live, "integrity.json"), "{}\n");
|
|
@@ -642,7 +642,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t
|
|
|
642
642
|
const fixture = await hardenedWindowsGoFixture(packageRoot, { blockInstall: true });
|
|
643
643
|
const first = installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
|
|
644
644
|
await fixture.waitForInstall();
|
|
645
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v4.
|
|
645
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
|
|
646
646
|
await assert.rejects(
|
|
647
647
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }),
|
|
648
648
|
(error: unknown) => error instanceof Error && error.message.includes(lockPath),
|
|
@@ -656,7 +656,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t
|
|
|
656
656
|
|
|
657
657
|
test("Windows source publication rolls back a prior bundle when final directory swap fails", async () => {
|
|
658
658
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-rollback-"));
|
|
659
|
-
const versionDirectory = join(packageRoot, ".gentle-ai", "v4.
|
|
659
|
+
const versionDirectory = join(packageRoot, ".gentle-ai", "v4.2.0");
|
|
660
660
|
await mkdir(versionDirectory, { recursive: true });
|
|
661
661
|
await writeFile(join(versionDirectory, "old.txt"), "previous bundle");
|
|
662
662
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
@@ -1092,7 +1092,7 @@ test("Darwin/Linux signed bundles retain their four-field manifest and reusable
|
|
|
1092
1092
|
},
|
|
1093
1093
|
};
|
|
1094
1094
|
await installGentleAi(options);
|
|
1095
|
-
const manifestPath = join(packageRoot, ".gentle-ai", "v4.
|
|
1095
|
+
const manifestPath = join(packageRoot, ".gentle-ai", "v4.2.0", "integrity.json");
|
|
1096
1096
|
const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as Record<string, string>;
|
|
1097
1097
|
assert.deepEqual(Object.keys(manifest), ["version", "asset", "assetSha256", "binarySha256"]);
|
|
1098
1098
|
assert.equal((await installGentleAi({ ...options, download: async () => { throw new Error("signed bundle must be reused"); } })).installed, false);
|
|
@@ -1105,7 +1105,7 @@ test("a pinned asset falls back to its gentle-pi mirror only on download failure
|
|
|
1105
1105
|
// under the current stable pin, whose form gate refuses raw binaries.
|
|
1106
1106
|
const payload = Buffer.from("signed archive fixture");
|
|
1107
1107
|
const binary = "mirrored binary";
|
|
1108
|
-
const baseAsset = { name: "gentle-ai_4.
|
|
1108
|
+
const baseAsset = { name: "gentle-ai_4.2.0_linux_amd64.tar.gz", sha256: createHash("sha256").update(payload).digest("hex"), binarySha256: createHash("sha256").update(binary).digest("hex"), url: "https://example.invalid/upstream", mirrorUrl: "https://example.invalid/mirror", executable: "gentle-ai" };
|
|
1109
1109
|
const extractArchive = async (_archive: string, destination: string) => {
|
|
1110
1110
|
await mkdir(destination, { recursive: true });
|
|
1111
1111
|
await writeFile(join(destination, "gentle-ai"), binary);
|
|
@@ -1249,7 +1249,7 @@ test("checksum mismatch cleans temporary state without promoting a binary", asyn
|
|
|
1249
1249
|
}),
|
|
1250
1250
|
/checksum mismatch/,
|
|
1251
1251
|
);
|
|
1252
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.
|
|
1252
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
|
|
1253
1253
|
assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.startsWith(".gentle-ai-install-")), []);
|
|
1254
1254
|
});
|
|
1255
1255
|
|
|
@@ -1271,7 +1271,7 @@ test("installer promotes only the expected regular executable with executable PO
|
|
|
1271
1271
|
await chmod(extracted, 0o700);
|
|
1272
1272
|
},
|
|
1273
1273
|
});
|
|
1274
|
-
const binary = join(packageRoot, ".gentle-ai", "v4.
|
|
1274
|
+
const binary = join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai");
|
|
1275
1275
|
assert.equal(existsSync(binary), true);
|
|
1276
1276
|
assert.equal(await readFile(binary, "utf8"), "native executable");
|
|
1277
1277
|
assert.ok(((await stat(binary)).mode & 0o111) !== 0);
|
|
@@ -1302,7 +1302,7 @@ test("installer rejects an extracted binary that differs from its pinned digest"
|
|
|
1302
1302
|
}),
|
|
1303
1303
|
/binary checksum mismatch/,
|
|
1304
1304
|
);
|
|
1305
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.
|
|
1305
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
|
|
1306
1306
|
});
|
|
1307
1307
|
|
|
1308
1308
|
test("installer repairs a valid non-executable POSIX binary instead of reusing it", async (t) => {
|
|
@@ -1325,7 +1325,7 @@ test("installer repairs a valid non-executable POSIX binary instead of reusing i
|
|
|
1325
1325
|
},
|
|
1326
1326
|
};
|
|
1327
1327
|
await installGentleAi(options);
|
|
1328
|
-
const binary = join(packageRoot, ".gentle-ai", "v4.
|
|
1328
|
+
const binary = join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai");
|
|
1329
1329
|
await chmod(binary, 0o600);
|
|
1330
1330
|
const repaired = await installGentleAi(options);
|
|
1331
1331
|
assert.equal(repaired.installed, true);
|
|
@@ -1362,7 +1362,7 @@ test("installer rejects archives with multiple expected executable entries", asy
|
|
|
1362
1362
|
}),
|
|
1363
1363
|
/exactly one regular gentle-ai/,
|
|
1364
1364
|
);
|
|
1365
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.
|
|
1365
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
|
|
1366
1366
|
});
|
|
1367
1367
|
|
|
1368
1368
|
test("installer rejects an archive without the expected regular executable", async () => {
|
|
@@ -1380,7 +1380,7 @@ test("installer rejects an archive without the expected regular executable", asy
|
|
|
1380
1380
|
}),
|
|
1381
1381
|
/non-regular gentle-ai/,
|
|
1382
1382
|
);
|
|
1383
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.
|
|
1383
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
|
|
1384
1384
|
});
|
|
1385
1385
|
|
|
1386
1386
|
test("safeRemoveDirectory passes Windows-safe retry options to recursive rm cleanup", async () => {
|