gentle-pi 2.1.2 → 2.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 +108 -79
- package/assets/agents/gentle-ai-worker.md +7 -3
- package/assets/agents/jd-fix-agent.md +1 -1
- package/assets/agents/jd-judge-a.md +3 -1
- package/assets/agents/jd-judge-b.md +3 -1
- package/assets/agents/review-readability.md +4 -3
- package/assets/agents/review-reliability.md +4 -3
- package/assets/agents/review-resilience.md +4 -3
- package/assets/agents/review-risk.md +4 -3
- package/assets/agents/sdd-apply.md +11 -4
- package/assets/agents/sdd-archive.md +6 -1
- package/assets/agents/sdd-design.md +6 -1
- package/assets/agents/sdd-explore.md +6 -2
- package/assets/agents/sdd-init.md +10 -2
- package/assets/agents/sdd-onboard.md +6 -1
- package/assets/agents/sdd-proposal.md +8 -1
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-spec.md +6 -1
- package/assets/agents/sdd-status.md +10 -5
- package/assets/agents/sdd-sync.md +6 -1
- package/assets/agents/sdd-tasks.md +8 -4
- package/assets/agents/sdd-verify.md +27 -2
- package/assets/chains/4r-review.chain.md +2 -0
- package/assets/chains/sdd-full.chain.md +2 -2
- package/assets/chains/sdd-plan.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +2 -2
- package/assets/orchestrator-delegation.md +145 -160
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +25 -48
- package/assets/sdd-orchestrator-workflow.md +163 -25
- package/assets/support/sdd-status-contract.md +24 -6
- package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
- package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
- package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
- package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
- package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
- package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
- package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
- package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
- package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
- package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
- package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
- package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
- package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
- package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
- package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
- package/docs/native-authority-architecture.md +14 -16
- package/docs/review-integration.md +27 -353
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +2710 -3211
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +21 -12
- package/extensions/skill-registry.ts +10 -2
- package/extensions/startup-banner.ts +10 -4
- package/lib/gentle-ai-binary.ts +173 -2
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +676 -781
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/provider-contract-bundle.ts +704 -0
- package/lib/review-candidate-view.ts +836 -118
- package/lib/review-compact-contract.ts +59 -248
- package/lib/review-host-relay.ts +578 -0
- package/lib/review-integration-v2.ts +1254 -221
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/review-relay-contract.ts +16 -0
- package/lib/sdd-preflight.ts +230 -67
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -79
- package/runtime/gentle-ai-binary.mjs +174 -3
- package/runtime/native-review-cli.mjs +610 -715
- package/runtime/review-integration-v2.mjs +1223 -190
- package/runtime/review-relay-contract.mjs +17 -0
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +5 -5
- package/scripts/check-provider-contract.mjs +138 -0
- package/scripts/gentle-ai-installer.mjs +85 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +403 -0
- package/scripts/mirror-provider-contract.mjs +143 -0
- package/scripts/test-packed-runner.mjs +20 -9
- package/scripts/verify-package-files.mjs +119 -43
- package/skills/_shared/review-ledger-contract.md +11 -19
- package/skills/chained-pr/SKILL.md +3 -0
- package/skills/cognitive-doc-design/SKILL.md +1 -1
- package/skills/comment-writer/SKILL.md +1 -1
- package/skills/gentle-ai/SKILL.md +7 -74
- package/skills/issue-creation/SKILL.md +94 -168
- package/skills/judgment-day/SKILL.md +9 -5
- package/skills/judgment-day/references/prompts-and-formats.md +2 -0
- package/skills/rdd-defect-workflow/SKILL.md +54 -0
- package/skills/release/SKILL.md +3 -3
- package/skills/skill-registry/SKILL.md +1 -1
- package/skills/work-unit-commits/SKILL.md +3 -1
- package/tests/artifact-language.test.ts +24 -11
- package/tests/ask-user-choice.test.ts +264 -0
- package/tests/background-subagents.test.ts +771 -0
- package/tests/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +16 -0
- package/tests/delegated-key-learnings-contract.test.ts +240 -0
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
- package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
- package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
- package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
- package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
- package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
- package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
- package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
- package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
- package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
- package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
- package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
- package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
- package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-ai-binary.test.ts +83 -13
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
- package/tests/gentle-ai-dev-binary.test.ts +159 -0
- package/tests/gentle-ai-installer.test.ts +138 -48
- package/tests/gentle-ai.test.ts +820 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/maintainer/provider-relay.maintest.ts +601 -0
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +75 -2
- package/tests/native-review-cli.test.ts +588 -908
- package/tests/native-review-consent.test.ts +283 -38
- package/tests/native-review-parity-runtime.test.ts +108 -350
- package/tests/native-review-parity.test.ts +684 -732
- package/tests/native-sdd-attempt-authority.test.ts +235 -0
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +158 -89
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +128 -100
- package/tests/provider-contract-bundle.test.ts +385 -0
- package/tests/provider-contract-mirror.test.ts +206 -0
- package/tests/provider-defect-handoff.test.ts +252 -0
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-actor-tool-deny.test.ts +12 -13
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +898 -12
- package/tests/review-compact-contract.test.ts +29 -122
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +366 -857
- package/tests/review-controller-native-routing.test.ts +1253 -4199
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +236 -70
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +134 -0
- package/tests/review-dispatch-hydration-gap.test.ts +145 -0
- package/tests/review-gate.test.ts +0 -45
- package/tests/review-host-relay-restart-parity.test.ts +360 -0
- package/tests/review-host-relay-routing.test.ts +352 -0
- package/tests/review-host-relay.test.ts +754 -0
- package/tests/review-integration-v2-forward.test.ts +654 -0
- package/tests/review-integration-v2.test.ts +317 -129
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +106 -60
- package/tests/review-recovered-lineage-routing.test.ts +199 -0
- package/tests/review-relay-transport-agent.test.ts +322 -0
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +573 -180
- package/tests/sdd-agent-tools.test.ts +53 -32
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +6 -8
- package/tests/skill-registry.test.ts +50 -1
- package/tests/verify-package-files.test.ts +62 -0
- package/tests/writer-edit-surface-scope.test.ts +230 -0
- package/themes/Gentleman-Cute.json +94 -0
- package/themes/Gentleman-Sexy.json +92 -0
- package/assets/agents/review-refuter.md +0 -40
- package/assets/agents/review-validator.md +0 -23
- package/lib/git-commit-transaction.ts +0 -801
- package/lib/native-review-remediation.ts +0 -49
- package/lib/review-compact.ts +0 -947
- package/lib/review-refuter-adapter.ts +0 -129
- package/lib/review-runtime-contract.ts +0 -68
- package/prompts/gcl.md +0 -54
- package/prompts/gis.md +0 -25
- package/prompts/gpr.md +0 -41
- package/prompts/gwr.md +0 -31
- package/runtime/git-commit-transaction.mjs +0 -802
- package/scripts/run-git-commit-transaction.mjs +0 -35
- package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
- package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
- package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
- package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
- package/tests/git-commit-transaction.test.ts +0 -302
- package/tests/review-compact.test.ts +0 -243
- package/tests/review-refuter-adapter.test.ts +0 -89
|
@@ -13,20 +13,21 @@ import {
|
|
|
13
13
|
GENTLE_AI_RELEASE_ASSETS,
|
|
14
14
|
GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM,
|
|
15
15
|
downloadGentleAiAsset,
|
|
16
|
+
gentleAiAssetForm,
|
|
16
17
|
installGentleAi,
|
|
17
18
|
resolveGentleAiInstallerPackageRoot,
|
|
18
19
|
resolveGentleAiReleaseAsset,
|
|
19
20
|
trustedSystemExtractor,
|
|
20
21
|
} from "../scripts/gentle-ai-installer.mjs";
|
|
21
22
|
|
|
22
|
-
// v2.
|
|
23
|
-
// checksums.txt and freshly computed hashes; binary
|
|
24
|
-
// from the extracted executables
|
|
23
|
+
// v2.5.0 digests pinned from the published release: archive digests verified
|
|
24
|
+
// against the minisign-signed checksums.txt and freshly computed hashes; binary
|
|
25
|
+
// digests computed from the extracted executables.
|
|
25
26
|
const EXPECTED_ASSETS = {
|
|
26
|
-
"darwin/amd64": { name: "gentle-ai_2.
|
|
27
|
-
"darwin/arm64": { name: "gentle-ai_2.
|
|
28
|
-
"linux/amd64": { name: "gentle-ai_2.
|
|
29
|
-
"linux/arm64": { name: "gentle-ai_2.
|
|
27
|
+
"darwin/amd64": { name: "gentle-ai_2.5.0_darwin_amd64.tar.gz", sha256: "8204ed692d7ff16fae2eb8bd7b3132b5e4e4dd7b9ea40e93bfced57394da27c0", binarySha256: "929817d320e94ad62be7ed546ad8746e62bd63ab97e3f38c94456f221f163eca" },
|
|
28
|
+
"darwin/arm64": { name: "gentle-ai_2.5.0_darwin_arm64.tar.gz", sha256: "ce06831cea9fd2ac7b2b36d9e98e63988246e0fbafbbd2cc7c003ec2f1463a23", binarySha256: "c859cd88138eb41e83ef9b75309697aaff2acb9f720c9a029b432095f66aea02" },
|
|
29
|
+
"linux/amd64": { name: "gentle-ai_2.5.0_linux_amd64.tar.gz", sha256: "2ba84a3a7ba2b1193019bde2acb05b02cbf222b667568c919686352b3caab113", binarySha256: "5acedd14b7927a7665636bf728c14a614597ce85dd91313bc50cbc96881c6cab" },
|
|
30
|
+
"linux/arm64": { name: "gentle-ai_2.5.0_linux_arm64.tar.gz", sha256: "16a6243d17c146e3fc0024f6f005c5bd141fbb2dda4b56e269318e499ddc170b", binarySha256: "ce75ba1174955813ef462f4e7a50dd59a8e8167575033f6e17aa8d8488630cb8" },
|
|
30
31
|
} as const;
|
|
31
32
|
|
|
32
33
|
test("default installer package root is the package containing scripts, not its parent", () => {
|
|
@@ -37,15 +38,31 @@ test("default installer package root is the package containing scripts, not its
|
|
|
37
38
|
assert.notEqual(resolveGentleAiInstallerPackageRoot(), dirname(expectedPackageRoot));
|
|
38
39
|
});
|
|
39
40
|
|
|
40
|
-
test("release mapping selects only the supported official v2.
|
|
41
|
+
test("release mapping selects only the supported official v2.5.0 assets and pinned digests", () => {
|
|
41
42
|
assert.deepEqual(
|
|
42
43
|
Object.fromEntries(Object.entries(GENTLE_AI_RELEASE_ASSETS).map(([key, asset]) => [key, { name: asset.name, sha256: asset.sha256, binarySha256: asset.binarySha256 }])),
|
|
43
44
|
EXPECTED_ASSETS,
|
|
44
45
|
);
|
|
45
|
-
assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_2.
|
|
46
|
-
assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_2.
|
|
46
|
+
assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_2.5.0_linux_amd64.tar.gz");
|
|
47
|
+
assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_2.5.0_darwin_arm64.tar.gz");
|
|
47
48
|
for (const asset of Object.values(GENTLE_AI_RELEASE_ASSETS)) {
|
|
48
|
-
assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v2\.
|
|
49
|
+
assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v2\.5\.0\//);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("raw release assets are admitted only under a prerelease pin", () => {
|
|
54
|
+
assert.equal(gentleAiAssetForm("gentle-ai_2.4.0_linux_amd64.tar.gz", "2.4.0"), "archive");
|
|
55
|
+
assert.equal(gentleAiAssetForm("gentle-ai_2.5.0-rc.3_linux_amd64", "2.5.0-rc.3"), "raw-binary");
|
|
56
|
+
assert.equal(gentleAiAssetForm("gentle-ai_2.5.0-rc.3_windows_amd64.exe", "2.5.0-rc.3"), "raw-binary");
|
|
57
|
+
// A raw binary under a stable pin means the pin itself is wrong: stable
|
|
58
|
+
// releases publish signed archives only, so this fails closed pre-download.
|
|
59
|
+
assert.throws(() => gentleAiAssetForm("gentle-ai_2.6.0_linux_amd64", "2.6.0"), /only admitted for a prerelease pin/);
|
|
60
|
+
assert.throws(() => gentleAiAssetForm("gentle-ai.dmg", "2.5.0-rc.3"), /unsupported Gentle AI release asset form/);
|
|
61
|
+
// The current stable pin admits every pinned asset row through the same
|
|
62
|
+
// gate the installer uses at download time (default installerVersion
|
|
63
|
+
// argument): signed archives, never raw binaries.
|
|
64
|
+
for (const asset of Object.values(GENTLE_AI_RELEASE_ASSETS)) {
|
|
65
|
+
assert.equal(gentleAiAssetForm(asset.name), "archive");
|
|
49
66
|
}
|
|
50
67
|
});
|
|
51
68
|
|
|
@@ -66,7 +83,7 @@ test("release digests are all-or-none and install fails closed while any digest
|
|
|
66
83
|
}),
|
|
67
84
|
/checksum mismatch/,
|
|
68
85
|
);
|
|
69
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.
|
|
86
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.5.0", "gentle-ai")), false);
|
|
70
87
|
}
|
|
71
88
|
});
|
|
72
89
|
|
|
@@ -95,7 +112,7 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) {
|
|
|
95
112
|
const metadata = [
|
|
96
113
|
"gentle-ai.exe: go1.25.10",
|
|
97
114
|
"\tpath\tgithub.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai",
|
|
98
|
-
`\tmod\tgithub.com/gentleman-programming/gentle-ai/v2\tv2.
|
|
115
|
+
`\tmod\tgithub.com/gentleman-programming/gentle-ai/v2\tv2.5.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
|
|
99
116
|
"\tbuild\t-buildmode=exe", "\tbuild\t-compiler=gc", "\tbuild\tCGO_ENABLED=0", `\tbuild\tGOARCH=${fixtureOptions.goArchitecture ?? "amd64"}`, "\tbuild\tGOOS=windows",
|
|
100
117
|
].join("\n");
|
|
101
118
|
const run = async (file: string, arguments_: string[], options: WindowsGoCall["options"]) => {
|
|
@@ -113,7 +130,7 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) {
|
|
|
113
130
|
return { stdout: "", stderr: "" };
|
|
114
131
|
}
|
|
115
132
|
if (file === goExecutable && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" };
|
|
116
|
-
if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 2.
|
|
133
|
+
if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 2.5.0\n", stderr: "" };
|
|
117
134
|
throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`);
|
|
118
135
|
};
|
|
119
136
|
return { calls, run, setGoExecutable: (path: string) => { goExecutable = path; } };
|
|
@@ -129,7 +146,7 @@ test("win32 x64 and arm64 install the exact Go SumDB source tag without archive
|
|
|
129
146
|
const result = await installGentleAi({ packageRoot, platform: "win32", arch, execFile: fixture.run, resolveGoExecutable: async () => goPath });
|
|
130
147
|
assert.equal(result.installed, true);
|
|
131
148
|
assert.deepEqual(fixture.calls.filter((call) => call.file === goPath).map((call) => call.arguments_.slice(0, 2)), [
|
|
132
|
-
["version"], ["install", "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@v2.
|
|
149
|
+
["version"], ["install", "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@v2.5.0"], ["version", "-m"],
|
|
133
150
|
]);
|
|
134
151
|
}
|
|
135
152
|
});
|
|
@@ -148,7 +165,7 @@ test("Windows source install reports missing or too-old Go without publishing a
|
|
|
148
165
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }),
|
|
149
166
|
(error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode,
|
|
150
167
|
);
|
|
151
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.
|
|
168
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.5.0", "gentle-ai.exe")), false);
|
|
152
169
|
assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.includes("install-")), []);
|
|
153
170
|
}
|
|
154
171
|
});
|
|
@@ -167,7 +184,7 @@ test("Windows source install cleans staging after Go failure or wrong built vers
|
|
|
167
184
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }),
|
|
168
185
|
(error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode,
|
|
169
186
|
);
|
|
170
|
-
const runtimeDirectory = join(packageRoot, ".gentle-ai", "v2.
|
|
187
|
+
const runtimeDirectory = join(packageRoot, ".gentle-ai", "v2.5.0");
|
|
171
188
|
assert.ok(fixture.calls.some((call) => call.arguments_[0] === "install"));
|
|
172
189
|
assert.equal(existsSync(join(runtimeDirectory, "gentle-ai.exe")), false);
|
|
173
190
|
assert.equal(existsSync(runtimeDirectory) && (await readdir(runtimeDirectory)).some((entry) => entry.startsWith(".go-install-") || entry.endsWith(".tmp")), false);
|
|
@@ -188,7 +205,7 @@ test("Windows source installs reuse only a fully verified package-local binary",
|
|
|
188
205
|
assert.equal(reused.installed, false);
|
|
189
206
|
assert.ok(reuse.calls.every((call) => call.file !== "gentle-ai"), "the installer must never fall back to ambient gentle-ai on PATH");
|
|
190
207
|
|
|
191
|
-
await writeFile(join(packageRoot, ".gentle-ai", "v2.
|
|
208
|
+
await writeFile(join(packageRoot, ".gentle-ai", "v2.5.0", "integrity.json"), "{}\n");
|
|
192
209
|
const repaired = windowsGoFixture();
|
|
193
210
|
repaired.setGoExecutable(goPath);
|
|
194
211
|
assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: repaired.run, resolveGoExecutable: async () => goPath })).installed, true);
|
|
@@ -230,7 +247,7 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har
|
|
|
230
247
|
const metadata = fixtureOptions.metadataOverride ?? [
|
|
231
248
|
"gentle-ai.exe: go1.25.10",
|
|
232
249
|
"\tpath\tgithub.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai",
|
|
233
|
-
`\tmod\tgithub.com/gentleman-programming/gentle-ai/v2\tv2.
|
|
250
|
+
`\tmod\tgithub.com/gentleman-programming/gentle-ai/v2\tv2.5.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
|
|
234
251
|
"\tbuild\t-buildmode=exe",
|
|
235
252
|
"\tbuild\t-compiler=gc",
|
|
236
253
|
"\tbuild\tCGO_ENABLED=0",
|
|
@@ -250,7 +267,7 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har
|
|
|
250
267
|
return { stdout: "", stderr: "" };
|
|
251
268
|
}
|
|
252
269
|
if (file === goPath && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" };
|
|
253
|
-
if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 2.
|
|
270
|
+
if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 2.5.0\n", stderr: "" };
|
|
254
271
|
throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`);
|
|
255
272
|
};
|
|
256
273
|
return {
|
|
@@ -292,7 +309,7 @@ test("Windows source installation resolves one validated Go executable and seals
|
|
|
292
309
|
const goCalls = fixture.calls.filter((call) => call.file === fixture.goPath);
|
|
293
310
|
assert.deepEqual(goCalls.map((call) => call.arguments_.slice(0, 2)), [
|
|
294
311
|
["version"],
|
|
295
|
-
["install", "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@v2.
|
|
312
|
+
["install", "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@v2.5.0"],
|
|
296
313
|
["version", "-m"],
|
|
297
314
|
]);
|
|
298
315
|
for (const call of goCalls) {
|
|
@@ -318,14 +335,14 @@ test("Windows source manifest binds verified Go metadata and architecture", asyn
|
|
|
318
335
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-provenance-"));
|
|
319
336
|
const fixture = await hardenedWindowsGoFixture(packageRoot, { architecture: "arm64" });
|
|
320
337
|
const result = await installGentleAi({ packageRoot, platform: "win32", arch: "arm64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
|
|
321
|
-
const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v2.
|
|
338
|
+
const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v2.5.0", "integrity.json"), "utf8")) as Record<string, string>;
|
|
322
339
|
assert.equal(result.installed, true);
|
|
323
340
|
assert.deepEqual(manifest, {
|
|
324
|
-
version: "2.
|
|
341
|
+
version: "2.5.0",
|
|
325
342
|
method: "go-sumdb-source-build",
|
|
326
343
|
package: "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai",
|
|
327
344
|
module: "github.com/gentleman-programming/gentle-ai/v2",
|
|
328
|
-
tag: "v2.
|
|
345
|
+
tag: "v2.5.0",
|
|
329
346
|
architecture: "arm64",
|
|
330
347
|
binarySha256: createHash("sha256").update("trusted Windows source build").digest("hex"),
|
|
331
348
|
moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM,
|
|
@@ -345,12 +362,12 @@ test("Windows source installation rejects Go metadata for a different architectu
|
|
|
345
362
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }),
|
|
346
363
|
(error: unknown) => error instanceof Error && "code" in error && error.code === "GENTLE_AI_GO_INSTALL_FAILED",
|
|
347
364
|
);
|
|
348
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.
|
|
365
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.5.0", "gentle-ai.exe")), false);
|
|
349
366
|
});
|
|
350
367
|
|
|
351
368
|
test("Windows source installation treats a fresh ownerless lock as active", async () => {
|
|
352
369
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-lock-"));
|
|
353
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v2.
|
|
370
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v2.5.0.install.lock");
|
|
354
371
|
await mkdir(lockPath, { recursive: true });
|
|
355
372
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
356
373
|
await assertManualLockRecoveryRequired(packageRoot, fixture);
|
|
@@ -358,7 +375,7 @@ test("Windows source installation treats a fresh ownerless lock as active", asyn
|
|
|
358
375
|
|
|
359
376
|
test("Windows source installation preserves a lock whose owner nonce changed before release", async () => {
|
|
360
377
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owner-lock-"));
|
|
361
|
-
const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v2.
|
|
378
|
+
const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v2.5.0.install.lock", "owner.json");
|
|
362
379
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
363
380
|
let replacedOwner = false;
|
|
364
381
|
const run = async (...arguments_: Parameters<typeof fixture.run>) => {
|
|
@@ -375,7 +392,7 @@ test("Windows source installation preserves a lock whose owner nonce changed bef
|
|
|
375
392
|
});
|
|
376
393
|
|
|
377
394
|
async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Awaited<ReturnType<typeof hardenedWindowsGoFixture>>, options: Record<string, unknown> = {}) {
|
|
378
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v2.
|
|
395
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v2.5.0.install.lock");
|
|
379
396
|
await assert.rejects(
|
|
380
397
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable, ...options }),
|
|
381
398
|
(error: unknown) => error instanceof Error && error.message.includes(lockPath) && /confirm no installer is active.*remove.*manually/i.test(error.message),
|
|
@@ -385,18 +402,18 @@ async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Aw
|
|
|
385
402
|
}
|
|
386
403
|
|
|
387
404
|
function tombstonePath(packageRoot: string, nonce: string): string {
|
|
388
|
-
return join(packageRoot, ".gentle-ai", `.v2.
|
|
405
|
+
return join(packageRoot, ".gentle-ai", `.v2.5.0.install.tombstone-${nonce}`);
|
|
389
406
|
}
|
|
390
407
|
|
|
391
408
|
async function tombstones(packageRoot: string): Promise<string[]> {
|
|
392
409
|
const runtimeRoot = join(packageRoot, ".gentle-ai");
|
|
393
410
|
return existsSync(runtimeRoot)
|
|
394
|
-
? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v2.
|
|
411
|
+
? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v2.5.0.install.tombstone-"))
|
|
395
412
|
: [];
|
|
396
413
|
}
|
|
397
414
|
|
|
398
415
|
function backupBundlePath(packageRoot: string, nonce: string): string {
|
|
399
|
-
return join(packageRoot, ".gentle-ai", `.v2.
|
|
416
|
+
return join(packageRoot, ".gentle-ai", `.v2.5.0.backup-${nonce}`);
|
|
400
417
|
}
|
|
401
418
|
|
|
402
419
|
async function copyWindowsBundle(source: string, destination: string): Promise<void> {
|
|
@@ -407,7 +424,7 @@ async function copyWindowsBundle(source: string, destination: string): Promise<v
|
|
|
407
424
|
|
|
408
425
|
test("Windows source installation fails closed for an ownerless lock even after the stale threshold", async () => {
|
|
409
426
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-stale-lock-"));
|
|
410
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v2.
|
|
427
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v2.5.0.install.lock");
|
|
411
428
|
await mkdir(lockPath, { recursive: true });
|
|
412
429
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
413
430
|
await assertManualLockRecoveryRequired(packageRoot, fixture, { now: () => Date.now() + 10 * 60_000 });
|
|
@@ -415,7 +432,7 @@ test("Windows source installation fails closed for an ownerless lock even after
|
|
|
415
432
|
|
|
416
433
|
test("Windows source installation fails closed for a stale owner lock", async () => {
|
|
417
434
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-stale-lock-"));
|
|
418
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v2.
|
|
435
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v2.5.0.install.lock");
|
|
419
436
|
await mkdir(lockPath, { recursive: true });
|
|
420
437
|
await writeFile(join(lockPath, "owner.json"), `${JSON.stringify({ createdAt: 0, nonce: "0".repeat(64) })}\n`);
|
|
421
438
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
@@ -467,7 +484,7 @@ test("Windows source acquisition fails closed when a tombstone appears after its
|
|
|
467
484
|
(error: unknown) => error instanceof Error && error.message.includes(foreignTombstone),
|
|
468
485
|
);
|
|
469
486
|
assert.equal(existsSync(foreignTombstone), true);
|
|
470
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v2.
|
|
487
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v2.5.0.install.lock")), false);
|
|
471
488
|
assert.equal(fixture.calls.some((call) => call.arguments_[0] === "install"), false);
|
|
472
489
|
});
|
|
473
490
|
|
|
@@ -475,7 +492,7 @@ test("Windows source release deletes its matching tombstone and allows reuse", a
|
|
|
475
492
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owned-lock-release-"));
|
|
476
493
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
477
494
|
await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
|
|
478
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v2.
|
|
495
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v2.5.0.install.lock")), false);
|
|
479
496
|
assert.deepEqual(await tombstones(packageRoot), []);
|
|
480
497
|
const reuse = await hardenedWindowsGoFixture(packageRoot);
|
|
481
498
|
assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: reuse.run, resolveGoExecutable: reuse.resolveGoExecutable })).installed, false);
|
|
@@ -485,7 +502,7 @@ test("Windows source recovers a valid backup after a crash between publication r
|
|
|
485
502
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-crash-"));
|
|
486
503
|
const initial = await hardenedWindowsGoFixture(packageRoot);
|
|
487
504
|
await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
|
|
488
|
-
const live = join(packageRoot, ".gentle-ai", "v2.
|
|
505
|
+
const live = join(packageRoot, ".gentle-ai", "v2.5.0");
|
|
489
506
|
const backup = backupBundlePath(packageRoot, "crash");
|
|
490
507
|
await rename(live, backup);
|
|
491
508
|
const recovery = await hardenedWindowsGoFixture(packageRoot);
|
|
@@ -521,7 +538,7 @@ test("Windows source cleans one validated backup only when the live bundle is va
|
|
|
521
538
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-valid-live-"));
|
|
522
539
|
const initial = await hardenedWindowsGoFixture(packageRoot);
|
|
523
540
|
await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
|
|
524
|
-
const live = join(packageRoot, ".gentle-ai", "v2.
|
|
541
|
+
const live = join(packageRoot, ".gentle-ai", "v2.5.0");
|
|
525
542
|
const backup = backupBundlePath(packageRoot, "valid");
|
|
526
543
|
await copyWindowsBundle(live, backup);
|
|
527
544
|
const reuse = await hardenedWindowsGoFixture(packageRoot);
|
|
@@ -534,7 +551,7 @@ test("Windows source preserves a valid backup when the live bundle is invalid",
|
|
|
534
551
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-invalid-live-"));
|
|
535
552
|
const initial = await hardenedWindowsGoFixture(packageRoot);
|
|
536
553
|
await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
|
|
537
|
-
const live = join(packageRoot, ".gentle-ai", "v2.
|
|
554
|
+
const live = join(packageRoot, ".gentle-ai", "v2.5.0");
|
|
538
555
|
const backup = backupBundlePath(packageRoot, "valid");
|
|
539
556
|
await copyWindowsBundle(live, backup);
|
|
540
557
|
await writeFile(join(live, "integrity.json"), "{}\n");
|
|
@@ -552,7 +569,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t
|
|
|
552
569
|
const fixture = await hardenedWindowsGoFixture(packageRoot, { blockInstall: true });
|
|
553
570
|
const first = installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
|
|
554
571
|
await fixture.waitForInstall();
|
|
555
|
-
const lockPath = join(packageRoot, ".gentle-ai", ".v2.
|
|
572
|
+
const lockPath = join(packageRoot, ".gentle-ai", ".v2.5.0.install.lock");
|
|
556
573
|
await assert.rejects(
|
|
557
574
|
() => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }),
|
|
558
575
|
(error: unknown) => error instanceof Error && error.message.includes(lockPath),
|
|
@@ -566,7 +583,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t
|
|
|
566
583
|
|
|
567
584
|
test("Windows source publication rolls back a prior bundle when final directory swap fails", async () => {
|
|
568
585
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-rollback-"));
|
|
569
|
-
const versionDirectory = join(packageRoot, ".gentle-ai", "v2.
|
|
586
|
+
const versionDirectory = join(packageRoot, ".gentle-ai", "v2.5.0");
|
|
570
587
|
await mkdir(versionDirectory, { recursive: true });
|
|
571
588
|
await writeFile(join(versionDirectory, "old.txt"), "previous bundle");
|
|
572
589
|
const fixture = await hardenedWindowsGoFixture(packageRoot);
|
|
@@ -590,7 +607,7 @@ test("Windows source publication rolls back a prior bundle when final directory
|
|
|
590
607
|
test("Darwin/Linux signed bundles retain their four-field manifest and reusable compatibility", async () => {
|
|
591
608
|
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-signed-compatibility-"));
|
|
592
609
|
const payload = Buffer.from("signed archive fixture");
|
|
593
|
-
const asset = { name: "gentle-ai_2.
|
|
610
|
+
const asset = { name: "gentle-ai_2.4.0_linux_amd64.tar.gz", sha256: createHash("sha256").update(payload).digest("hex"), url: "https://example.invalid/gentle-ai.tar.gz", executable: "gentle-ai" };
|
|
594
611
|
const options = {
|
|
595
612
|
packageRoot,
|
|
596
613
|
platform: "linux",
|
|
@@ -603,12 +620,66 @@ test("Darwin/Linux signed bundles retain their four-field manifest and reusable
|
|
|
603
620
|
},
|
|
604
621
|
};
|
|
605
622
|
await installGentleAi(options);
|
|
606
|
-
const manifestPath = join(packageRoot, ".gentle-ai", "v2.
|
|
623
|
+
const manifestPath = join(packageRoot, ".gentle-ai", "v2.5.0", "integrity.json");
|
|
607
624
|
const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as Record<string, string>;
|
|
608
625
|
assert.deepEqual(Object.keys(manifest), ["version", "asset", "assetSha256", "binarySha256"]);
|
|
609
626
|
assert.equal((await installGentleAi({ ...options, download: async () => { throw new Error("signed bundle must be reused"); } })).installed, false);
|
|
610
627
|
});
|
|
611
628
|
|
|
629
|
+
test("a pinned asset falls back to its gentle-pi mirror only on download failure, and a stable pin declares no mirror", async () => {
|
|
630
|
+
// The mirror fallback is a property of the pinned row, not of the asset
|
|
631
|
+
// form: a prerelease row carries a mirrorUrl, a stable row never does.
|
|
632
|
+
// Drive the fallback through an archive-form asset so the same test holds
|
|
633
|
+
// under the current stable pin, whose form gate refuses raw binaries.
|
|
634
|
+
const payload = Buffer.from("signed archive fixture");
|
|
635
|
+
const binary = "mirrored binary";
|
|
636
|
+
const baseAsset = { name: "gentle-ai_2.5.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" };
|
|
637
|
+
const extractArchive = async (_archive: string, destination: string) => {
|
|
638
|
+
await mkdir(destination, { recursive: true });
|
|
639
|
+
await writeFile(join(destination, "gentle-ai"), binary);
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
const attempted: string[] = [];
|
|
643
|
+
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-mirror-"));
|
|
644
|
+
const result = await installGentleAi({
|
|
645
|
+
packageRoot, platform: "linux", arch: "x64",
|
|
646
|
+
releaseAssets: { "linux/amd64": baseAsset },
|
|
647
|
+
extractArchive,
|
|
648
|
+
download: async (url: string, destination: string) => {
|
|
649
|
+
attempted.push(url);
|
|
650
|
+
if (url === baseAsset.url) throw new Error("upstream asset deleted");
|
|
651
|
+
await writeFile(destination, payload);
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
assert.equal(result.installed, true);
|
|
655
|
+
assert.deepEqual(attempted, [baseAsset.url, baseAsset.mirrorUrl]);
|
|
656
|
+
|
|
657
|
+
// Integrity beats availability: wrong bytes from the upstream source fail
|
|
658
|
+
// immediately and the mirror is never consulted.
|
|
659
|
+
const mismatched: string[] = [];
|
|
660
|
+
await assert.rejects(installGentleAi({
|
|
661
|
+
packageRoot: await mkdtemp(join(tmpdir(), "gentle-pi-installer-mirror-mismatch-")), platform: "linux", arch: "x64",
|
|
662
|
+
releaseAssets: { "linux/amd64": baseAsset },
|
|
663
|
+
extractArchive,
|
|
664
|
+
download: async (url: string, destination: string) => { mismatched.push(url); await writeFile(destination, "tampered bytes"); },
|
|
665
|
+
}), /checksum mismatch/);
|
|
666
|
+
assert.deepEqual(mismatched, [baseAsset.url]);
|
|
667
|
+
|
|
668
|
+
// Both sources unavailable: the failure names the documented recovery.
|
|
669
|
+
await assert.rejects(installGentleAi({
|
|
670
|
+
packageRoot: await mkdtemp(join(tmpdir(), "gentle-pi-installer-mirror-unavailable-")), platform: "linux", arch: "x64",
|
|
671
|
+
releaseAssets: { "linux/amd64": baseAsset },
|
|
672
|
+
extractArchive,
|
|
673
|
+
download: async () => { throw new Error("gone"); },
|
|
674
|
+
}), (error: unknown) => error instanceof Error && /GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1/.test(error.message) && (error as { code?: string }).code === "GENTLE_AI_ASSET_UNAVAILABLE");
|
|
675
|
+
|
|
676
|
+
// A stable pin downloads only from the signed gentle-ai release: no pinned
|
|
677
|
+
// row carries a mirror, so an upstream failure surfaces as the primary error.
|
|
678
|
+
for (const pinned of Object.values(GENTLE_AI_RELEASE_ASSETS)) {
|
|
679
|
+
assert.equal((pinned as { mirrorUrl?: string }).mirrorUrl, undefined);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
612
683
|
test("Windows archive lookup remains unsupported and names Go SumDB source installation", () => {
|
|
613
684
|
for (const arch of ["x64", "arm64"]) {
|
|
614
685
|
assert.throws(() => resolveGentleAiReleaseAsset("win32", arch), /Go SumDB source installation/);
|
|
@@ -680,7 +751,7 @@ test("checksum mismatch cleans temporary state without promoting a binary", asyn
|
|
|
680
751
|
}),
|
|
681
752
|
/checksum mismatch/,
|
|
682
753
|
);
|
|
683
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.
|
|
754
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.5.0", "gentle-ai")), false);
|
|
684
755
|
assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.startsWith(".gentle-ai-install-")), []);
|
|
685
756
|
});
|
|
686
757
|
|
|
@@ -702,7 +773,7 @@ test("installer promotes only the expected regular executable with executable PO
|
|
|
702
773
|
await chmod(extracted, 0o700);
|
|
703
774
|
},
|
|
704
775
|
});
|
|
705
|
-
const binary = join(packageRoot, ".gentle-ai", "v2.
|
|
776
|
+
const binary = join(packageRoot, ".gentle-ai", "v2.5.0", "gentle-ai");
|
|
706
777
|
assert.equal(existsSync(binary), true);
|
|
707
778
|
assert.equal(await readFile(binary, "utf8"), "native executable");
|
|
708
779
|
assert.ok(((await stat(binary)).mode & 0o111) !== 0);
|
|
@@ -733,7 +804,7 @@ test("installer rejects an extracted binary that differs from its pinned digest"
|
|
|
733
804
|
}),
|
|
734
805
|
/binary checksum mismatch/,
|
|
735
806
|
);
|
|
736
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.
|
|
807
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.5.0", "gentle-ai")), false);
|
|
737
808
|
});
|
|
738
809
|
|
|
739
810
|
test("installer repairs a valid non-executable POSIX binary instead of reusing it", async (t) => {
|
|
@@ -756,7 +827,7 @@ test("installer repairs a valid non-executable POSIX binary instead of reusing i
|
|
|
756
827
|
},
|
|
757
828
|
};
|
|
758
829
|
await installGentleAi(options);
|
|
759
|
-
const binary = join(packageRoot, ".gentle-ai", "v2.
|
|
830
|
+
const binary = join(packageRoot, ".gentle-ai", "v2.5.0", "gentle-ai");
|
|
760
831
|
await chmod(binary, 0o600);
|
|
761
832
|
const repaired = await installGentleAi(options);
|
|
762
833
|
assert.equal(repaired.installed, true);
|
|
@@ -793,7 +864,7 @@ test("installer rejects archives with multiple expected executable entries", asy
|
|
|
793
864
|
}),
|
|
794
865
|
/exactly one regular gentle-ai/,
|
|
795
866
|
);
|
|
796
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.
|
|
867
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.5.0", "gentle-ai")), false);
|
|
797
868
|
});
|
|
798
869
|
|
|
799
870
|
test("installer rejects an archive without the expected regular executable", async () => {
|
|
@@ -811,5 +882,24 @@ test("installer rejects an archive without the expected regular executable", asy
|
|
|
811
882
|
}),
|
|
812
883
|
/non-regular gentle-ai/,
|
|
813
884
|
);
|
|
814
|
-
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.
|
|
885
|
+
assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.5.0", "gentle-ai")), false);
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// #400: a clean Windows source build measured 232 s on a cold module cache,
|
|
889
|
+
// so `go install` cannot share the 120 s bound that fits the `go version`
|
|
890
|
+
// probes. The probes keep their bound; the build gets its own.
|
|
891
|
+
test("Windows source build bounds go install separately from the Go toolchain probes", async () => {
|
|
892
|
+
const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-windows-timeout-"));
|
|
893
|
+
const fixture = windowsGoFixture();
|
|
894
|
+
const goPath = join(packageRoot, "go.exe");
|
|
895
|
+
await writeFile(goPath, "trusted local Go executable");
|
|
896
|
+
fixture.setGoExecutable(goPath);
|
|
897
|
+
await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath });
|
|
898
|
+
const goCalls = fixture.calls.filter((call) => call.file === goPath);
|
|
899
|
+
const install = goCalls.find((call) => call.arguments_[0] === "install");
|
|
900
|
+
assert.ok(install, "go install must run");
|
|
901
|
+
assert.ok(typeof install.options.timeout === "number" && install.options.timeout >= 600_000, `go install must get at least a ten-minute bound, received ${install.options.timeout}`);
|
|
902
|
+
const probes = goCalls.filter((call) => call.arguments_[0] === "version");
|
|
903
|
+
assert.ok(probes.length >= 2, "go version probes must run");
|
|
904
|
+
for (const probe of probes) assert.equal(probe.options.timeout, 120_000, `go ${probe.arguments_.join(" ")} keeps the probe bound`);
|
|
815
905
|
});
|