gentle-pi 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,7 +15,7 @@ The [v2.6.0 release](https://github.com/Gentleman-Programming/gentle-pi/releases
15
15
  - The Agents List and Details views preserve the orchestrator/session hierarchy and completion, abort, and lost-exit history. Parent-child queries and notifications have an explicit handoff path, while model, effort, and usage stay observable per task.
16
16
  - Named `/gentle:profiles` atomically route the orchestrator separately from packaged and review roles; see the [technical reference](readme-reference.md#agent-model-profiles) for the profile model.
17
17
 
18
- The source checkout currently prepares `gentle-pi` `3.0.0` with a package-local Gentle AI `v2.9.1` pin; this is not a claim that `3.0.0` is published.
18
+ The source checkout currently prepares `gentle-pi` `3.1.0` with a package-local Gentle AI `v3.0.1` pin; this is not a claim that `3.1.0` is published.
19
19
 
20
20
  ## Shell interactions and runtime behavior
21
21
 
@@ -99,7 +99,7 @@ This is guidance through existing tools, not a new CLI, phase, state engine, or
99
99
  | **Skill creation workflow** | Provides the `gentle-ai-skill-creator`/`gentle-ai-skill-improver` skills, `/skill-creation` prompt, and packaged style guide for LLM-first skills. |
100
100
  | **Delivery skills** | Includes issue-first PRs, chained PRs, work-unit commits, cognitive docs, comment writing, and Judgment Day review. |
101
101
  | **Bounded native review** | Freezes one candidate, dispatches only controller-selected lenses, and records native authority. Review outcomes are informational; delivery follows ordinary repository policy. |
102
- | **Verified native runtime** | The current source checkout provisions the exact package-local Gentle AI v2.9.1 runtime: signed, SHA-256-pinned release archives on Darwin/Linux and a Go SumDB-verified source build on Windows x64/arm64. It validates package-local integrity and rejects PATH, global, sibling, symlink, and mode fallbacks. |
102
+ | **Verified native runtime** | The current source checkout provisions the exact package-local Gentle AI v3.0.1 runtime: signed, SHA-256-pinned release archives on Darwin/Linux and a Go SumDB-verified source build on Windows x64/arm64. It validates package-local integrity and rejects PATH, global, sibling, symlink, and mode fallbacks. |
103
103
  | **Runtime safety** | Blocks destructive shell commands, asks for confirmation for sensitive operations, and blocks direct read/write/edit access to sensitive paths. |
104
104
 
105
105
  ## Native pointer regions
@@ -136,7 +136,7 @@ The stable release is [`v2.6.0`](https://github.com/Gentleman-Programming/gentle
136
136
 
137
137
  ### Source checkout
138
138
 
139
- This checkout prepares `gentle-pi` `3.0.0`; it is source state, not a published release. Its package-local native runtime pin is Gentle AI `v2.9.1`, distinct from the published `v2.6.0` pairing.
139
+ This checkout prepares `gentle-pi` `3.1.0`; it is source state, not a published release. Its package-local native runtime pin is Gentle AI `v3.0.1`, distinct from the published `v2.6.0` pairing.
140
140
 
141
141
  The native SDD status consumer accepts both the pinned producer's legacy
142
142
  `apply`/`verify`/`remediate`/`archive` instruction record and the classical
@@ -147,7 +147,7 @@ Unknown or incomplete instruction records still fail closed.
147
147
  The Pi runtime now uses native status exclusively for SDD and retires standalone
148
148
  sync. The full chain follows completed apply to archive, where applicable delta
149
149
  specs are composed; verification remains explicitly invokable. With the current
150
- 2.9.1 pin, native still requires verification and its emitted evidence requirements;
150
+ 3.0.1 pin, native still requires verification and its emitted evidence requirements;
151
151
  a plain practical PASS report does not satisfy that legacy native gate. Pi forwards
152
152
  those exact instructions without overriding readiness or inventing legacy evidence.
153
153
  Classical direct-archive behavior is compatibility-tested with an identified
@@ -181,7 +181,7 @@ pi install npm:gentle-pi@2.6.0
181
181
 
182
182
  RDD remains opt-in. Enable it only through an explicit user decision with `/gentle:review-mode enable`; `status` lets you inspect the mode without changing it.
183
183
 
184
- The source checkout's RDD integration installs Gentle AI only into its private `.gentle-ai/` directory. Darwin and Linux use pinned release assets with asset and executable SHA-256 verification (signed archives for source pin `v2.9.1`; raw prerelease binaries only under a prerelease pin). Windows x64 and arm64 build the exact `v2.9.1` source tag with a local Go 1.25.10+ toolchain, a sealed Go environment, `GOTOOLCHAIN=local`, and `GOSUMDB=sum.golang.org`; it does not download Go automatically. Windows provenance is Go-toolchain plus SumDB evidence and postinstall tamper detection, **not** Authenticode or protection against a malicious joint binary-and-manifest replacement. Package-private locks coordinate cooperative concurrent or crashed installers; their tombstones fail closed. A malicious same-user process with write access to package-private `node_modules` is outside that protocol because it can already replace package code, binary, or manifest, and portable Node has no pathname-delete CAS. It never uses `PATH` or a global `gentle-ai` installation. For development or offline installs only, set `GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1`; native review operations then fail closed with an actionable `package-local-binary-missing` error. To recover explicitly, if `GENTLE_PI_SKIP_GENTLE_AI_INSTALL` is set, remove or unset it before changing to the installed `gentle-pi` package directory. Then run `node scripts/install-gentle-ai.mjs`. This invokes the package-owned installer without relying on a global binary or npm configuration change. A missing binary can result from skipped lifecycle scripts, but does not prove that lifecycle scripts were disabled.
184
+ The source checkout's RDD integration installs Gentle AI only into its private `.gentle-ai/` directory. Darwin and Linux use pinned release assets with asset and executable SHA-256 verification (signed archives for source pin `v3.0.1`; raw prerelease binaries only under a prerelease pin). Windows x64 and arm64 build the exact `v3.0.1` source tag with a local Go 1.25.10+ toolchain, a sealed Go environment, `GOTOOLCHAIN=local`, and `GOSUMDB=sum.golang.org`; it does not download Go automatically. Windows provenance is Go-toolchain plus SumDB evidence and postinstall tamper detection, **not** Authenticode or protection against a malicious joint binary-and-manifest replacement. Package-private locks coordinate cooperative concurrent or crashed installers; their tombstones fail closed. A malicious same-user process with write access to package-private `node_modules` is outside that protocol because it can already replace package code, binary, or manifest, and portable Node has no pathname-delete CAS. It never uses `PATH` or a global `gentle-ai` installation. For development or offline installs only, set `GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1`; native review operations then fail closed with an actionable `package-local-binary-missing` error. To recover explicitly, if `GENTLE_PI_SKIP_GENTLE_AI_INSTALL` is set, remove or unset it before changing to the installed `gentle-pi` package directory. Then run `node scripts/install-gentle-ai.mjs`. This invokes the package-owned installer without relying on a global binary or npm configuration change. A missing binary can result from skipped lifecycle scripts, but does not prove that lifecycle scripts were disabled.
185
185
 
186
186
  Recommended companion packages:
187
187
 
@@ -338,13 +338,13 @@ flowchart TD
338
338
 
339
339
  VALIDATE is informational. Commit, push, PR, and release commands follow ordinary repository policy; RDD never authorizes, rewrites, consumes review state for, or blocks them. Dangerous-command safety and destructive-review consent remain independent.
340
340
 
341
- For the source checkout, native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v2.9.1 executable, independently hashes it, then negotiates `gentle-ai.review-integration/v2` outside the repository. Capabilities are cached by that executable digest. Every START, target status, FINALIZE, validate, and BIND-SDD request passes the same contract identifier. Negotiated envelopes decode exactly against the vendored schemas; `recover` routes only the provider-selected `action_disposition`, and optional additions require a future compatible schema/minor that the provider explicitly advertises and the consumer negotiates.
341
+ For the source checkout, native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v3.0.1 executable, independently hashes it, then negotiates `gentle-ai.review-integration/v2` outside the repository. Capabilities are cached by that executable digest. Every START, target status, FINALIZE, validate, and BIND-SDD request passes the same contract identifier. Negotiated envelopes decode exactly against the vendored schemas; `recover` routes only the provider-selected `action_disposition`, and optional additions require a future compatible schema/minor that the provider explicitly advertises and the consumer negotiates.
342
342
 
343
343
  Contract `/v2` replaces the Base64 `candidate_diff` reviewer transport of `/v1` with immutable `base_tree`/`candidate_tree` plus an ordered `changed_path_manifest` and never an inline patch. `gentle-pi` negotiates `/v2` only, with no dual-lane fallback; the cutover landed as one atomic commit against gentle-ai v2.2.2 (tracked by the `migrate-review-integration-v2` change), and the `/v1` schemas stay packaged because the `/v2` schemas `$ref` into their fragments. This provider contract version is unrelated to Pi's own internal "compact-v2" review-authority naming used below — the shared digit is coincidental, not a version pairing.
344
344
 
345
345
  Target status owns `current_target`, `unrelated`, `ambiguous`, and `corrupted` applicability and returns one native action. Pi does not reconstruct ordinary authority from provider-private files or choose a lineage from repository-wide history. Restart recovery rebuilds only the derived candidate view from the native Git/content projection, including intended-untracked paths, symlinks, and immutable gitlink identities. Native failure envelopes retain their exact mutation outcome, replayability, required inputs, request digest, and next action. After an unknown or lost mutating result, Pi calls target status before any replay decision and returns only the provider-declared action.
346
346
 
347
- Once the source checkout's pinned gentle-ai runtime (currently v2.9.1) has written review authority, rollback MUST preserve every native store and receipt and MUST NOT run a downgraded binary against that repository. Disable the Pi route or roll forward to a compatible authority-aware release instead; deleting authority data or reinstalling an older binary is not a rollback path.
347
+ Once the source checkout's pinned gentle-ai runtime (currently v3.0.1) has written review authority, rollback MUST preserve every native store and receipt and MUST NOT run a downgraded binary against that repository. Disable the Pi route or roll forward to a compatible authority-aware release instead; deleting authority data or reinstalling an older binary is not a rollback path.
348
348
 
349
349
  ### FINALIZE wrapper input
350
350
 
@@ -968,6 +968,24 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
968
968
  // exactly. riskEvidence and hint remain dark because neither is proven to
969
969
  // reach the negotiated START path Pi consumes.
970
970
  "2.9.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
971
+ // v3.0.0 shipped ODD as the orchestrator's mandatory default protocol and
972
+ // integrated the simplified SDD workflow into it (gentle-ai #4642, #4644),
973
+ // with the provider contract byte-frozen at 1.2.0. Ground-truthed by
974
+ // diffing contracts/review-integration/v2 and
975
+ // contracts/review-provider-contract between the v2.9.1 and v3.0.0 tags
976
+ // in the gentle-ai source tree: zero bytes changed. Neither change touches
977
+ // the closed START/STATUS fields this row negotiates, so it repeats 2.9.1
978
+ // exactly. riskEvidence and hint remain dark because neither is proven to
979
+ // reach the negotiated START path Pi consumes.
980
+ "3.0.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
981
+ // v3.0.1 moved the Go module path to github.com/gentleman-programming/gentle-ai/v3
982
+ // with no contract change (gentle-ai #4683). Ground-truthed by diffing
983
+ // contracts/review-integration/v2 and contracts/review-provider-contract
984
+ // between the v3.0.0 and v3.0.1 tags in the gentle-ai source tree: zero
985
+ // bytes changed. This row repeats 3.0.0 (and 2.9.1) exactly. riskEvidence
986
+ // and hint remain dark because neither is proven to reach the negotiated
987
+ // START path Pi consumes.
988
+ "3.0.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
971
989
  });
972
990
 
973
991
  export interface NativeReviewProcessDiagnostics {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gentle-pi",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -969,6 +969,24 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
969
969
  // exactly. riskEvidence and hint remain dark because neither is proven to
970
970
  // reach the negotiated START path Pi consumes.
971
971
  "2.9.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
972
+ // v3.0.0 shipped ODD as the orchestrator's mandatory default protocol and
973
+ // integrated the simplified SDD workflow into it (gentle-ai #4642, #4644),
974
+ // with the provider contract byte-frozen at 1.2.0. Ground-truthed by
975
+ // diffing contracts/review-integration/v2 and
976
+ // contracts/review-provider-contract between the v2.9.1 and v3.0.0 tags
977
+ // in the gentle-ai source tree: zero bytes changed. Neither change touches
978
+ // the closed START/STATUS fields this row negotiates, so it repeats 2.9.1
979
+ // exactly. riskEvidence and hint remain dark because neither is proven to
980
+ // reach the negotiated START path Pi consumes.
981
+ "3.0.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
982
+ // v3.0.1 moved the Go module path to github.com/gentleman-programming/gentle-ai/v3
983
+ // with no contract change (gentle-ai #4683). Ground-truthed by diffing
984
+ // contracts/review-integration/v2 and contracts/review-provider-contract
985
+ // between the v3.0.0 and v3.0.1 tags in the gentle-ai source tree: zero
986
+ // bytes changed. This row repeats 3.0.0 (and 2.9.1) exactly. riskEvidence
987
+ // and hint remain dark because neither is proven to reach the negotiated
988
+ // START path Pi consumes.
989
+ "3.0.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
972
990
  });
973
991
 
974
992
 
@@ -36,19 +36,19 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows";
36
36
  // version check below) derives from this constant instead of repeating the
37
37
  // literal, so a pin bump cannot leave a stale copy behind. See
38
38
  // scripts/install-gentle-ai.mjs for the incident that motivated this.
39
- export const INSTALLER_VERSION = "2.9.1";
39
+ export const INSTALLER_VERSION = "3.0.1";
40
40
  export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`;
41
41
  export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
42
42
  SIGNED_RELEASE_ASSET: "signed-release-asset",
43
43
  GO_SUMDB_SOURCE_BUILD: "go-sumdb-source-build",
44
44
  });
45
- export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai";
46
- export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v2";
45
+ export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai";
46
+ export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v3";
47
47
  export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`;
48
- // `go mod download -json github.com/gentleman-programming/gentle-ai/v2@v2.9.1`
48
+ // `go mod download -json github.com/gentleman-programming/gentle-ai/v3@v3.0.1`
49
49
  // with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the
50
- // tag resolves to commit 9ec0cf44, the published v2.9.1 release head.
51
- export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:roCuxlF+L4YOb1X0los4RpjcLfxAmwmHxteSQMcfThY=";
50
+ // tag resolves to commit 2594581e, the published v3.0.1 release head.
51
+ export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:r2z8yf0CdL0ZPPkMEKI3km6SitjOIC/DQDvtipc1Igs=";
52
52
  export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`;
53
53
  export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10";
54
54
  export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE";
@@ -67,7 +67,7 @@ export class GentleAiInstallerError extends Error {
67
67
  // Sentinel used while a re-pinned gentle-ai release is not yet published. A
68
68
  // sentinel digest can never match a real SHA-256, so installation fails closed,
69
69
  // and verify-package-files.mjs refuses to pack/publish while any digest below
70
- // still holds it. The v2.9.1 digests are pinned from the published release:
70
+ // still holds it. The v3.0.1 digests are pinned from the published release:
71
71
  // archive sha256 values verified against the minisign-signed checksums.txt and
72
72
  // freshly computed hashes; binary sha256 values computed from the extracted
73
73
  // executables.
@@ -109,15 +109,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) {
109
109
  }
110
110
 
111
111
  // Windows is absent from signed release archives on purpose. gentle-ai stopped
112
- // distributing unsigned Windows builds in c4b764d0, so v2.9.1 publishes signed
112
+ // distributing unsigned Windows builds in c4b764d0, so v3.0.1 publishes signed
113
113
  // Darwin/Linux archives only. Windows x64/arm64 uses the separately verified
114
114
  // exact-tag Go SumDB source-build path below; restore archive rows only when
115
115
  // upstream ships signed Windows assets.
116
116
  export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
117
- "darwin/amd64": asset("gentle-ai_2.9.1_darwin_amd64.tar.gz", "9e4b10c8e0ff36e51f39fad4da082f37b408234253cbbb7f8a8f710a5ee7b7f7", "96b2739365aa7513997e9ea5435cf38c019810dc3db0fb51c4c8f2fdb910de58", "gentle-ai"),
118
- "darwin/arm64": asset("gentle-ai_2.9.1_darwin_arm64.tar.gz", "d876c237e4559bea6849e9f9be33c63cea053154149df4a3e1e5983c9a6232ff", "5a675f666f52e4c070ad703da01a5e3ab6b1a4a002e3ac08ca1ef2871d21ac59", "gentle-ai"),
119
- "linux/amd64": asset("gentle-ai_2.9.1_linux_amd64.tar.gz", "07efddecd29fd1c3f8c8df19a3b6613c1948927d410b8502bf82b39b84d9481b", "8bd0161c51ed07e77801a92d13bdcb82ff483c273d4a182904534057d4054d97", "gentle-ai"),
120
- "linux/arm64": asset("gentle-ai_2.9.1_linux_arm64.tar.gz", "c3e5a173fa1b19be0abc0c6e5de18709cd1985cff61cc140546191e809324ed2", "a311c9ae6bc8d59931eee7c686be5d51b35d60b5d2b591e17e9b432968926e4e", "gentle-ai"),
117
+ "darwin/amd64": asset("gentle-ai_3.0.1_darwin_amd64.tar.gz", "fce899fbdada3781624cc03aa6adaf9b21041915240ef092ff89f222151c9efd", "01dec1aea73a0e70d44432d021bdafc9309382f597ce247f58e6c523caed1345", "gentle-ai"),
118
+ "darwin/arm64": asset("gentle-ai_3.0.1_darwin_arm64.tar.gz", "17f12008a056e43042daa6d72b37923a2a071f0d52cca80606410eb096caa9c2", "74c0f14c9806a786ae2cd78989d8ca95e0c01e6c7cc3b80c738f284c1064212e", "gentle-ai"),
119
+ "linux/amd64": asset("gentle-ai_3.0.1_linux_amd64.tar.gz", "920ad99189a655452006e96a7b25f1e04d745973e36b75130a3b4a16c53deda8", "201b79d4e6e7551cdfde3ad44e56ad0638173b1093f87fc1156a24652fa24448", "gentle-ai"),
120
+ "linux/arm64": asset("gentle-ai_3.0.1_linux_arm64.tar.gz", "e54a588c4ce5adfd9ded4c59c6cc5c1eb369fd88f8c54fa892f2cceb7fdbac8d", "9dbd481f5854c568411a8030fdd2192a23ff011270f8994ae181655786dca638", "gentle-ai"),
121
121
  });
122
122
 
123
123
  // A pinned asset is either a signed archive or, for a prerelease pin only,
@@ -339,7 +339,7 @@ async function main() {
339
339
  });
340
340
 
341
341
  if (driftedContracts.length > 0) {
342
- console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v2.9.1 runtime's vendored Gentle AI contract artifacts:");
342
+ console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v3.0.1 runtime's vendored Gentle AI contract artifacts:");
343
343
  for (const drift of driftedContracts) console.error(`- ${drift.relativePath}: expected ${drift.expected}, got ${drift.actual}`);
344
344
  process.exit(1);
345
345
  }
@@ -384,7 +384,7 @@ async function main() {
384
384
  process.exit(1);
385
385
  }
386
386
 
387
- console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v2.9.1 runtime).`);
387
+ console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v3.0.1 runtime).`);
388
388
  }
389
389
 
390
390
  const isMainModule = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
@@ -95,9 +95,9 @@ async function writeWindowsSourceBinary(packageRoot: string): Promise<{ binaryPa
95
95
  await writeFile(manifestPath, `${JSON.stringify({
96
96
  version: GENTLE_AI_VERSION,
97
97
  method: "go-sumdb-source-build",
98
- package: "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai",
99
- module: "github.com/gentleman-programming/gentle-ai/v2",
100
- tag: "v2.9.1",
98
+ package: "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai",
99
+ module: "github.com/gentleman-programming/gentle-ai/v3",
100
+ tag: "v3.0.1",
101
101
  architecture: process.arch === "x64" ? "x64" : "arm64",
102
102
  binarySha256: createHash("sha256").update(binary).digest("hex"),
103
103
  moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM,
@@ -20,14 +20,14 @@ import {
20
20
  trustedSystemExtractor,
21
21
  } from "../scripts/gentle-ai-installer.mjs";
22
22
 
23
- // v2.9.1 digests pinned from the published release: archive digests verified
23
+ // v3.0.1 digests pinned from the published release: archive digests verified
24
24
  // against the minisign-signed checksums.txt and freshly computed hashes; binary
25
25
  // digests computed from the extracted executables.
26
26
  const EXPECTED_ASSETS = {
27
- "darwin/amd64": { name: "gentle-ai_2.9.1_darwin_amd64.tar.gz", sha256: "9e4b10c8e0ff36e51f39fad4da082f37b408234253cbbb7f8a8f710a5ee7b7f7", binarySha256: "96b2739365aa7513997e9ea5435cf38c019810dc3db0fb51c4c8f2fdb910de58" },
28
- "darwin/arm64": { name: "gentle-ai_2.9.1_darwin_arm64.tar.gz", sha256: "d876c237e4559bea6849e9f9be33c63cea053154149df4a3e1e5983c9a6232ff", binarySha256: "5a675f666f52e4c070ad703da01a5e3ab6b1a4a002e3ac08ca1ef2871d21ac59" },
29
- "linux/amd64": { name: "gentle-ai_2.9.1_linux_amd64.tar.gz", sha256: "07efddecd29fd1c3f8c8df19a3b6613c1948927d410b8502bf82b39b84d9481b", binarySha256: "8bd0161c51ed07e77801a92d13bdcb82ff483c273d4a182904534057d4054d97" },
30
- "linux/arm64": { name: "gentle-ai_2.9.1_linux_arm64.tar.gz", sha256: "c3e5a173fa1b19be0abc0c6e5de18709cd1985cff61cc140546191e809324ed2", binarySha256: "a311c9ae6bc8d59931eee7c686be5d51b35d60b5d2b591e17e9b432968926e4e" },
27
+ "darwin/amd64": { name: "gentle-ai_3.0.1_darwin_amd64.tar.gz", sha256: "fce899fbdada3781624cc03aa6adaf9b21041915240ef092ff89f222151c9efd", binarySha256: "01dec1aea73a0e70d44432d021bdafc9309382f597ce247f58e6c523caed1345" },
28
+ "darwin/arm64": { name: "gentle-ai_3.0.1_darwin_arm64.tar.gz", sha256: "17f12008a056e43042daa6d72b37923a2a071f0d52cca80606410eb096caa9c2", binarySha256: "74c0f14c9806a786ae2cd78989d8ca95e0c01e6c7cc3b80c738f284c1064212e" },
29
+ "linux/amd64": { name: "gentle-ai_3.0.1_linux_amd64.tar.gz", sha256: "920ad99189a655452006e96a7b25f1e04d745973e36b75130a3b4a16c53deda8", binarySha256: "201b79d4e6e7551cdfde3ad44e56ad0638173b1093f87fc1156a24652fa24448" },
30
+ "linux/arm64": { name: "gentle-ai_3.0.1_linux_arm64.tar.gz", sha256: "e54a588c4ce5adfd9ded4c59c6cc5c1eb369fd88f8c54fa892f2cceb7fdbac8d", binarySha256: "9dbd481f5854c568411a8030fdd2192a23ff011270f8994ae181655786dca638" },
31
31
  } as const;
32
32
 
33
33
  test("default installer package root is the package containing scripts, not its parent", () => {
@@ -38,15 +38,15 @@ test("default installer package root is the package containing scripts, not its
38
38
  assert.notEqual(resolveGentleAiInstallerPackageRoot(), dirname(expectedPackageRoot));
39
39
  });
40
40
 
41
- test("release mapping selects only the supported official v2.9.1 assets and pinned digests", () => {
41
+ test("release mapping selects only the supported official v3.0.1 assets and pinned digests", () => {
42
42
  assert.deepEqual(
43
43
  Object.fromEntries(Object.entries(GENTLE_AI_RELEASE_ASSETS).map(([key, asset]) => [key, { name: asset.name, sha256: asset.sha256, binarySha256: asset.binarySha256 }])),
44
44
  EXPECTED_ASSETS,
45
45
  );
46
- assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_2.9.1_linux_amd64.tar.gz");
47
- assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_2.9.1_darwin_arm64.tar.gz");
46
+ assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_3.0.1_linux_amd64.tar.gz");
47
+ assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_3.0.1_darwin_arm64.tar.gz");
48
48
  for (const asset of Object.values(GENTLE_AI_RELEASE_ASSETS)) {
49
- assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v2\.9\.1\//);
49
+ assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v3\.0\.1\//);
50
50
  }
51
51
  });
52
52
 
@@ -56,7 +56,7 @@ test("raw release assets are admitted only under a prerelease pin", () => {
56
56
  assert.equal(gentleAiAssetForm("gentle-ai_2.5.0-rc.3_windows_amd64.exe", "2.5.0-rc.3"), "raw-binary");
57
57
  // A raw binary under a stable pin means the pin itself is wrong: stable
58
58
  // releases publish signed archives only, so this fails closed pre-download.
59
- assert.throws(() => gentleAiAssetForm("gentle-ai_2.9.1_linux_amd64", "2.9.1"), /only admitted for a prerelease pin/);
59
+ assert.throws(() => gentleAiAssetForm("gentle-ai_3.0.1_linux_amd64", "3.0.1"), /only admitted for a prerelease pin/);
60
60
  assert.throws(() => gentleAiAssetForm("gentle-ai.dmg", "2.5.0-rc.3"), /unsupported Gentle AI release asset form/);
61
61
  // The current stable pin admits every pinned asset row through the same
62
62
  // gate the installer uses at download time (default installerVersion
@@ -83,7 +83,7 @@ test("release digests are all-or-none and install fails closed while any digest
83
83
  }),
84
84
  /checksum mismatch/,
85
85
  );
86
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.9.1", "gentle-ai")), false);
86
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.0.1", "gentle-ai")), false);
87
87
  }
88
88
  });
89
89
 
@@ -111,8 +111,8 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) {
111
111
  let goExecutable = "go";
112
112
  const metadata = [
113
113
  "gentle-ai.exe: go1.25.10",
114
- "\tpath\tgithub.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai",
115
- `\tmod\tgithub.com/gentleman-programming/gentle-ai/v2\tv2.9.1\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
114
+ "\tpath\tgithub.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai",
115
+ `\tmod\tgithub.com/gentleman-programming/gentle-ai/v3\tv3.0.1\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
116
116
  "\tbuild\t-buildmode=exe", "\tbuild\t-compiler=gc", "\tbuild\tCGO_ENABLED=0", `\tbuild\tGOARCH=${fixtureOptions.goArchitecture ?? "amd64"}`, "\tbuild\tGOOS=windows",
117
117
  ].join("\n");
118
118
  const run = async (file: string, arguments_: string[], options: WindowsGoCall["options"]) => {
@@ -130,7 +130,7 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) {
130
130
  return { stdout: "", stderr: "" };
131
131
  }
132
132
  if (file === goExecutable && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" };
133
- if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 2.9.1\n", stderr: "" };
133
+ if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 3.0.1\n", stderr: "" };
134
134
  throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`);
135
135
  };
136
136
  return { calls, run, setGoExecutable: (path: string) => { goExecutable = path; } };
@@ -146,7 +146,7 @@ test("win32 x64 and arm64 install the exact Go SumDB source tag without archive
146
146
  const result = await installGentleAi({ packageRoot, platform: "win32", arch, execFile: fixture.run, resolveGoExecutable: async () => goPath });
147
147
  assert.equal(result.installed, true);
148
148
  assert.deepEqual(fixture.calls.filter((call) => call.file === goPath).map((call) => call.arguments_.slice(0, 2)), [
149
- ["version"], ["install", "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@v2.9.1"], ["version", "-m"],
149
+ ["version"], ["install", "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai@v3.0.1"], ["version", "-m"],
150
150
  ]);
151
151
  }
152
152
  });
@@ -165,7 +165,7 @@ test("Windows source install reports missing or too-old Go without publishing a
165
165
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }),
166
166
  (error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode,
167
167
  );
168
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.9.1", "gentle-ai.exe")), false);
168
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.0.1", "gentle-ai.exe")), false);
169
169
  assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.includes("install-")), []);
170
170
  }
171
171
  });
@@ -184,7 +184,7 @@ test("Windows source install cleans staging after Go failure or wrong built vers
184
184
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }),
185
185
  (error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode,
186
186
  );
187
- const runtimeDirectory = join(packageRoot, ".gentle-ai", "v2.9.1");
187
+ const runtimeDirectory = join(packageRoot, ".gentle-ai", "v3.0.1");
188
188
  assert.ok(fixture.calls.some((call) => call.arguments_[0] === "install"));
189
189
  assert.equal(existsSync(join(runtimeDirectory, "gentle-ai.exe")), false);
190
190
  assert.equal(existsSync(runtimeDirectory) && (await readdir(runtimeDirectory)).some((entry) => entry.startsWith(".go-install-") || entry.endsWith(".tmp")), false);
@@ -205,7 +205,7 @@ test("Windows source installs reuse only a fully verified package-local binary",
205
205
  assert.equal(reused.installed, false);
206
206
  assert.ok(reuse.calls.every((call) => call.file !== "gentle-ai"), "the installer must never fall back to ambient gentle-ai on PATH");
207
207
 
208
- await writeFile(join(packageRoot, ".gentle-ai", "v2.9.1", "integrity.json"), "{}\n");
208
+ await writeFile(join(packageRoot, ".gentle-ai", "v3.0.1", "integrity.json"), "{}\n");
209
209
  const repaired = windowsGoFixture();
210
210
  repaired.setGoExecutable(goPath);
211
211
  assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: repaired.run, resolveGoExecutable: async () => goPath })).installed, true);
@@ -246,8 +246,8 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har
246
246
  : undefined;
247
247
  const metadata = fixtureOptions.metadataOverride ?? [
248
248
  "gentle-ai.exe: go1.25.10",
249
- "\tpath\tgithub.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai",
250
- `\tmod\tgithub.com/gentleman-programming/gentle-ai/v2\tv2.9.1\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
249
+ "\tpath\tgithub.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai",
250
+ `\tmod\tgithub.com/gentleman-programming/gentle-ai/v3\tv3.0.1\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
251
251
  "\tbuild\t-buildmode=exe",
252
252
  "\tbuild\t-compiler=gc",
253
253
  "\tbuild\tCGO_ENABLED=0",
@@ -267,7 +267,7 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har
267
267
  return { stdout: "", stderr: "" };
268
268
  }
269
269
  if (file === goPath && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" };
270
- if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 2.9.1\n", stderr: "" };
270
+ if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 3.0.1\n", stderr: "" };
271
271
  throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`);
272
272
  };
273
273
  return {
@@ -309,7 +309,7 @@ test("Windows source installation resolves one validated Go executable and seals
309
309
  const goCalls = fixture.calls.filter((call) => call.file === fixture.goPath);
310
310
  assert.deepEqual(goCalls.map((call) => call.arguments_.slice(0, 2)), [
311
311
  ["version"],
312
- ["install", "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@v2.9.1"],
312
+ ["install", "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai@v3.0.1"],
313
313
  ["version", "-m"],
314
314
  ]);
315
315
  for (const call of goCalls) {
@@ -335,14 +335,14 @@ test("Windows source manifest binds verified Go metadata and architecture", asyn
335
335
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-provenance-"));
336
336
  const fixture = await hardenedWindowsGoFixture(packageRoot, { architecture: "arm64" });
337
337
  const result = await installGentleAi({ packageRoot, platform: "win32", arch: "arm64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
338
- const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v2.9.1", "integrity.json"), "utf8")) as Record<string, string>;
338
+ const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v3.0.1", "integrity.json"), "utf8")) as Record<string, string>;
339
339
  assert.equal(result.installed, true);
340
340
  assert.deepEqual(manifest, {
341
- version: "2.9.1",
341
+ version: "3.0.1",
342
342
  method: "go-sumdb-source-build",
343
- package: "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai",
344
- module: "github.com/gentleman-programming/gentle-ai/v2",
345
- tag: "v2.9.1",
343
+ package: "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai",
344
+ module: "github.com/gentleman-programming/gentle-ai/v3",
345
+ tag: "v3.0.1",
346
346
  architecture: "arm64",
347
347
  binarySha256: createHash("sha256").update("trusted Windows source build").digest("hex"),
348
348
  moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM,
@@ -362,12 +362,12 @@ test("Windows source installation rejects Go metadata for a different architectu
362
362
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }),
363
363
  (error: unknown) => error instanceof Error && "code" in error && error.code === "GENTLE_AI_GO_INSTALL_FAILED",
364
364
  );
365
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.9.1", "gentle-ai.exe")), false);
365
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.0.1", "gentle-ai.exe")), false);
366
366
  });
367
367
 
368
368
  test("Windows source installation treats a fresh ownerless lock as active", async () => {
369
369
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-lock-"));
370
- const lockPath = join(packageRoot, ".gentle-ai", ".v2.9.1.install.lock");
370
+ const lockPath = join(packageRoot, ".gentle-ai", ".v3.0.1.install.lock");
371
371
  await mkdir(lockPath, { recursive: true });
372
372
  const fixture = await hardenedWindowsGoFixture(packageRoot);
373
373
  await assertManualLockRecoveryRequired(packageRoot, fixture);
@@ -375,7 +375,7 @@ test("Windows source installation treats a fresh ownerless lock as active", asyn
375
375
 
376
376
  test("Windows source installation preserves a lock whose owner nonce changed before release", async () => {
377
377
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owner-lock-"));
378
- const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v2.9.1.install.lock", "owner.json");
378
+ const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v3.0.1.install.lock", "owner.json");
379
379
  const fixture = await hardenedWindowsGoFixture(packageRoot);
380
380
  let replacedOwner = false;
381
381
  const run = async (...arguments_: Parameters<typeof fixture.run>) => {
@@ -392,7 +392,7 @@ test("Windows source installation preserves a lock whose owner nonce changed bef
392
392
  });
393
393
 
394
394
  async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Awaited<ReturnType<typeof hardenedWindowsGoFixture>>, options: Record<string, unknown> = {}) {
395
- const lockPath = join(packageRoot, ".gentle-ai", ".v2.9.1.install.lock");
395
+ const lockPath = join(packageRoot, ".gentle-ai", ".v3.0.1.install.lock");
396
396
  await assert.rejects(
397
397
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable, ...options }),
398
398
  (error: unknown) => error instanceof Error && error.message.includes(lockPath) && /confirm no installer is active.*remove.*manually/i.test(error.message),
@@ -402,18 +402,18 @@ async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Aw
402
402
  }
403
403
 
404
404
  function tombstonePath(packageRoot: string, nonce: string): string {
405
- return join(packageRoot, ".gentle-ai", `.v2.9.1.install.tombstone-${nonce}`);
405
+ return join(packageRoot, ".gentle-ai", `.v3.0.1.install.tombstone-${nonce}`);
406
406
  }
407
407
 
408
408
  async function tombstones(packageRoot: string): Promise<string[]> {
409
409
  const runtimeRoot = join(packageRoot, ".gentle-ai");
410
410
  return existsSync(runtimeRoot)
411
- ? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v2.9.1.install.tombstone-"))
411
+ ? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v3.0.1.install.tombstone-"))
412
412
  : [];
413
413
  }
414
414
 
415
415
  function backupBundlePath(packageRoot: string, nonce: string): string {
416
- return join(packageRoot, ".gentle-ai", `.v2.9.1.backup-${nonce}`);
416
+ return join(packageRoot, ".gentle-ai", `.v3.0.1.backup-${nonce}`);
417
417
  }
418
418
 
419
419
  async function copyWindowsBundle(source: string, destination: string): Promise<void> {
@@ -424,7 +424,7 @@ async function copyWindowsBundle(source: string, destination: string): Promise<v
424
424
 
425
425
  test("Windows source installation fails closed for an ownerless lock even after the stale threshold", async () => {
426
426
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-stale-lock-"));
427
- const lockPath = join(packageRoot, ".gentle-ai", ".v2.9.1.install.lock");
427
+ const lockPath = join(packageRoot, ".gentle-ai", ".v3.0.1.install.lock");
428
428
  await mkdir(lockPath, { recursive: true });
429
429
  const fixture = await hardenedWindowsGoFixture(packageRoot);
430
430
  await assertManualLockRecoveryRequired(packageRoot, fixture, { now: () => Date.now() + 10 * 60_000 });
@@ -432,7 +432,7 @@ test("Windows source installation fails closed for an ownerless lock even after
432
432
 
433
433
  test("Windows source installation fails closed for a stale owner lock", async () => {
434
434
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-stale-lock-"));
435
- const lockPath = join(packageRoot, ".gentle-ai", ".v2.9.1.install.lock");
435
+ const lockPath = join(packageRoot, ".gentle-ai", ".v3.0.1.install.lock");
436
436
  await mkdir(lockPath, { recursive: true });
437
437
  await writeFile(join(lockPath, "owner.json"), `${JSON.stringify({ createdAt: 0, nonce: "0".repeat(64) })}\n`);
438
438
  const fixture = await hardenedWindowsGoFixture(packageRoot);
@@ -484,7 +484,7 @@ test("Windows source acquisition fails closed when a tombstone appears after its
484
484
  (error: unknown) => error instanceof Error && error.message.includes(foreignTombstone),
485
485
  );
486
486
  assert.equal(existsSync(foreignTombstone), true);
487
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v2.9.1.install.lock")), false);
487
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v3.0.1.install.lock")), false);
488
488
  assert.equal(fixture.calls.some((call) => call.arguments_[0] === "install"), false);
489
489
  });
490
490
 
@@ -492,7 +492,7 @@ test("Windows source release deletes its matching tombstone and allows reuse", a
492
492
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owned-lock-release-"));
493
493
  const fixture = await hardenedWindowsGoFixture(packageRoot);
494
494
  await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
495
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v2.9.1.install.lock")), false);
495
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v3.0.1.install.lock")), false);
496
496
  assert.deepEqual(await tombstones(packageRoot), []);
497
497
  const reuse = await hardenedWindowsGoFixture(packageRoot);
498
498
  assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: reuse.run, resolveGoExecutable: reuse.resolveGoExecutable })).installed, false);
@@ -502,7 +502,7 @@ test("Windows source recovers a valid backup after a crash between publication r
502
502
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-crash-"));
503
503
  const initial = await hardenedWindowsGoFixture(packageRoot);
504
504
  await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
505
- const live = join(packageRoot, ".gentle-ai", "v2.9.1");
505
+ const live = join(packageRoot, ".gentle-ai", "v3.0.1");
506
506
  const backup = backupBundlePath(packageRoot, "crash");
507
507
  await rename(live, backup);
508
508
  const recovery = await hardenedWindowsGoFixture(packageRoot);
@@ -538,7 +538,7 @@ test("Windows source cleans one validated backup only when the live bundle is va
538
538
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-valid-live-"));
539
539
  const initial = await hardenedWindowsGoFixture(packageRoot);
540
540
  await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
541
- const live = join(packageRoot, ".gentle-ai", "v2.9.1");
541
+ const live = join(packageRoot, ".gentle-ai", "v3.0.1");
542
542
  const backup = backupBundlePath(packageRoot, "valid");
543
543
  await copyWindowsBundle(live, backup);
544
544
  const reuse = await hardenedWindowsGoFixture(packageRoot);
@@ -551,7 +551,7 @@ test("Windows source preserves a valid backup when the live bundle is invalid",
551
551
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-invalid-live-"));
552
552
  const initial = await hardenedWindowsGoFixture(packageRoot);
553
553
  await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
554
- const live = join(packageRoot, ".gentle-ai", "v2.9.1");
554
+ const live = join(packageRoot, ".gentle-ai", "v3.0.1");
555
555
  const backup = backupBundlePath(packageRoot, "valid");
556
556
  await copyWindowsBundle(live, backup);
557
557
  await writeFile(join(live, "integrity.json"), "{}\n");
@@ -569,7 +569,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t
569
569
  const fixture = await hardenedWindowsGoFixture(packageRoot, { blockInstall: true });
570
570
  const first = installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
571
571
  await fixture.waitForInstall();
572
- const lockPath = join(packageRoot, ".gentle-ai", ".v2.9.1.install.lock");
572
+ const lockPath = join(packageRoot, ".gentle-ai", ".v3.0.1.install.lock");
573
573
  await assert.rejects(
574
574
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }),
575
575
  (error: unknown) => error instanceof Error && error.message.includes(lockPath),
@@ -583,7 +583,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t
583
583
 
584
584
  test("Windows source publication rolls back a prior bundle when final directory swap fails", async () => {
585
585
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-rollback-"));
586
- const versionDirectory = join(packageRoot, ".gentle-ai", "v2.9.1");
586
+ const versionDirectory = join(packageRoot, ".gentle-ai", "v3.0.1");
587
587
  await mkdir(versionDirectory, { recursive: true });
588
588
  await writeFile(join(versionDirectory, "old.txt"), "previous bundle");
589
589
  const fixture = await hardenedWindowsGoFixture(packageRoot);
@@ -620,7 +620,7 @@ test("Darwin/Linux signed bundles retain their four-field manifest and reusable
620
620
  },
621
621
  };
622
622
  await installGentleAi(options);
623
- const manifestPath = join(packageRoot, ".gentle-ai", "v2.9.1", "integrity.json");
623
+ const manifestPath = join(packageRoot, ".gentle-ai", "v3.0.1", "integrity.json");
624
624
  const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as Record<string, string>;
625
625
  assert.deepEqual(Object.keys(manifest), ["version", "asset", "assetSha256", "binarySha256"]);
626
626
  assert.equal((await installGentleAi({ ...options, download: async () => { throw new Error("signed bundle must be reused"); } })).installed, false);
@@ -633,7 +633,7 @@ test("a pinned asset falls back to its gentle-pi mirror only on download failure
633
633
  // under the current stable pin, whose form gate refuses raw binaries.
634
634
  const payload = Buffer.from("signed archive fixture");
635
635
  const binary = "mirrored binary";
636
- const baseAsset = { name: "gentle-ai_2.9.1_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" };
636
+ const baseAsset = { name: "gentle-ai_3.0.1_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
637
  const extractArchive = async (_archive: string, destination: string) => {
638
638
  await mkdir(destination, { recursive: true });
639
639
  await writeFile(join(destination, "gentle-ai"), binary);
@@ -751,7 +751,7 @@ test("checksum mismatch cleans temporary state without promoting a binary", asyn
751
751
  }),
752
752
  /checksum mismatch/,
753
753
  );
754
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.9.1", "gentle-ai")), false);
754
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.0.1", "gentle-ai")), false);
755
755
  assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.startsWith(".gentle-ai-install-")), []);
756
756
  });
757
757
 
@@ -773,7 +773,7 @@ test("installer promotes only the expected regular executable with executable PO
773
773
  await chmod(extracted, 0o700);
774
774
  },
775
775
  });
776
- const binary = join(packageRoot, ".gentle-ai", "v2.9.1", "gentle-ai");
776
+ const binary = join(packageRoot, ".gentle-ai", "v3.0.1", "gentle-ai");
777
777
  assert.equal(existsSync(binary), true);
778
778
  assert.equal(await readFile(binary, "utf8"), "native executable");
779
779
  assert.ok(((await stat(binary)).mode & 0o111) !== 0);
@@ -804,7 +804,7 @@ test("installer rejects an extracted binary that differs from its pinned digest"
804
804
  }),
805
805
  /binary checksum mismatch/,
806
806
  );
807
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.9.1", "gentle-ai")), false);
807
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.0.1", "gentle-ai")), false);
808
808
  });
809
809
 
810
810
  test("installer repairs a valid non-executable POSIX binary instead of reusing it", async (t) => {
@@ -827,7 +827,7 @@ test("installer repairs a valid non-executable POSIX binary instead of reusing i
827
827
  },
828
828
  };
829
829
  await installGentleAi(options);
830
- const binary = join(packageRoot, ".gentle-ai", "v2.9.1", "gentle-ai");
830
+ const binary = join(packageRoot, ".gentle-ai", "v3.0.1", "gentle-ai");
831
831
  await chmod(binary, 0o600);
832
832
  const repaired = await installGentleAi(options);
833
833
  assert.equal(repaired.installed, true);
@@ -864,7 +864,7 @@ test("installer rejects archives with multiple expected executable entries", asy
864
864
  }),
865
865
  /exactly one regular gentle-ai/,
866
866
  );
867
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.9.1", "gentle-ai")), false);
867
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.0.1", "gentle-ai")), false);
868
868
  });
869
869
 
870
870
  test("installer rejects an archive without the expected regular executable", async () => {
@@ -882,7 +882,7 @@ test("installer rejects an archive without the expected regular executable", asy
882
882
  }),
883
883
  /non-regular gentle-ai/,
884
884
  );
885
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v2.9.1", "gentle-ai")), false);
885
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.0.1", "gentle-ai")), false);
886
886
  });
887
887
 
888
888
  // #400: a clean Windows source build measured 232 s on a cold module cache,
@@ -246,11 +246,40 @@ test("2.9.1 repeats 2.9.0 because the negotiated lane Pi consumes is unchanged",
246
246
  assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.9.0"] as Record<string, boolean>);
247
247
  });
248
248
 
249
+ test("3.0.0 repeats 2.9.1 because the negotiated lane Pi consumes is unchanged", () => {
250
+ // v3.0.0 shipped ODD as the orchestrator's mandatory default protocol and
251
+ // integrated the simplified SDD workflow into it (gentle-ai #4642, #4644),
252
+ // with the provider contract byte-frozen at 1.2.0. Diffing
253
+ // contracts/review-integration/v2 and contracts/review-provider-contract
254
+ // between the v2.9.1 and v3.0.0 tags in the gentle-ai source tree showed
255
+ // zero byte changes, so this row repeats 2.9.1. riskEvidence and hint
256
+ // remain dark because neither is proven to reach Pi's negotiated START
257
+ // path.
258
+ const contract = NATIVE_CLI_CONTRACTS["3.0.0"] as Record<string, boolean>;
259
+ assert.equal(contract.riskEvidence, false);
260
+ assert.equal(contract.hint, false);
261
+ assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.9.1"] as Record<string, boolean>);
262
+ });
263
+
264
+ test("3.0.1 repeats 3.0.0 because the negotiated lane Pi consumes is unchanged", () => {
265
+ // v3.0.1 moved the Go module path to
266
+ // github.com/gentleman-programming/gentle-ai/v3 with no contract change
267
+ // (gentle-ai #4683). Diffing contracts/review-integration/v2 and
268
+ // contracts/review-provider-contract between the v3.0.0 and v3.0.1 tags
269
+ // in the gentle-ai source tree showed zero byte changes, so this row
270
+ // repeats 3.0.0. riskEvidence and hint remain dark because neither is
271
+ // proven to reach Pi's negotiated START path.
272
+ const contract = NATIVE_CLI_CONTRACTS["3.0.1"] as Record<string, boolean>;
273
+ assert.equal(contract.riskEvidence, false);
274
+ assert.equal(contract.hint, false);
275
+ assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["3.0.0"] as Record<string, boolean>);
276
+ });
277
+
249
278
  test("no shipped version key was added beyond the pin bump", () => {
250
279
  // Rows are promises to consumers, so a new key only ever appears in a
251
280
  // dedicated commit alongside a pin bump, never as a side effect. v2.2.4 and
252
281
  // v2.3.0 shipped upstream while Pi stayed on 2.2.3 and were never pinned,
253
282
  // so they get no row: a row asserts ground truth measured against a binary
254
283
  // Pi actually ran, and the table only has to be ascending, not gapless.
255
- assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0", "2.5.0-rc.3", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.1"]);
284
+ assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0", "2.5.0-rc.3", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.1", "3.0.0", "3.0.1"]);
256
285
  });
@@ -309,20 +309,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
309
309
  );
310
310
  });
311
311
 
312
- test("package verification binds the published Gentle AI v2.9.1 runtime pin", () => {
312
+ test("package verification binds the published Gentle AI v3.0.1 runtime pin", () => {
313
313
  const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
314
314
  const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
315
315
  const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
316
316
 
317
- assert.match(installer, /INSTALLER_VERSION = "2\.9\.1"/);
317
+ assert.match(installer, /INSTALLER_VERSION = "3\.0\.1"/);
318
318
  assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
319
- assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:roCuxlF\+L4YOb1X0los4RpjcLfxAmwmHxteSQMcfThY="/);
319
+ assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:r2z8yf0CdL0ZPPkMEKI3km6SitjOIC\/DQDvtipc1Igs="/);
320
320
  assert.match(installer, /GOTOOLCHAIN: "local"/);
321
321
  assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
322
322
  assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
323
323
  assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
324
324
  assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
325
- assert.match(verifier, /v2\.9\.1/);
325
+ assert.match(verifier, /v3\.0\.1/);
326
326
  });
327
327
 
328
328
 
@@ -1574,9 +1574,9 @@ test("pi-pretty wrapper uses cached ESM loading for compiled and pnpm symlink in
1574
1574
  assert.match(wrapper, /quietToolsEnabled/);
1575
1575
  });
1576
1576
 
1577
- test("v3.0.0 release package and runtime stop before publication", () => {
1577
+ test("v3.1.0 release package and runtime stop before publication", () => {
1578
1578
  const packageJson = readPackageJson();
1579
- assert.equal(packageJson.version, "3.0.0", "the release manifest must remain explicitly pinned to v3.0.0");
1579
+ assert.equal(packageJson.version, "3.1.0", "the release manifest must remain explicitly pinned to v3.1.0");
1580
1580
  assert.equal(
1581
1581
  packageJson.scripts?.test,
1582
1582
  "node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",