diff --git a/docs/gentle-shell.md b/docs/gentle-shell.md index 886a172c3..6e628545c 100644 --- a/docs/gentle-shell.md +++ b/docs/gentle-shell.md @@ -15,7 +15,7 @@ The [v2.6.0 release](https://github.com/Gentleman-Programming/gentle-pi/releases - 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. - 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. -The source checkout prepares `gentle-pi` `3.7.0` with a package-local Gentle AI `v3.7.0` pin; this does not imply that the package release has been published. +The source checkout prepares `gentle-pi` `3.7.0` with a package-local Gentle AI `v4.0.0` pin; this does not imply that the package release has been published. ## Shell interactions and runtime behavior diff --git a/docs/readme-reference.md b/docs/readme-reference.md index 06c2e08ce..6780d82c0 100644 --- a/docs/readme-reference.md +++ b/docs/readme-reference.md @@ -114,7 +114,7 @@ This is guidance through existing tools, not a new CLI, phase, state engine, or | **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. | | **Delivery skills** | Includes issue-first PRs, chained PRs, work-unit commits, cognitive docs, comment writing, and Judgment Day review. | | **Bounded native review** | Freezes one candidate, dispatches only controller-selected lenses, and records native authority. Review outcomes are informational; delivery follows ordinary repository policy. | -| **Verified native runtime** | The current source checkout provisions the exact package-local Gentle AI v3.7.0 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. | +| **Verified native runtime** | The current source checkout provisions the exact package-local Gentle AI v4.0.0 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. | | **Runtime safety** | Blocks destructive shell commands, asks for confirmation for sensitive operations, and blocks direct read/write/edit access to sensitive paths. | ## Native pointer regions @@ -171,7 +171,7 @@ This installs the current npm release; select an explicit version if you need a ### Source checkout -This checkout declares `gentle-pi` `3.7.0` with a package-local Gentle AI `v3.7.0` pin. Checkout metadata alone is not proof of npm publication; verify the registry version and its release workflow. +This checkout declares `gentle-pi` `3.7.0` with a package-local Gentle AI `v4.0.0` pin. Checkout metadata alone is not proof of npm publication; verify the registry version and its release workflow. ### Pi compatibility @@ -198,7 +198,7 @@ pi install npm:gentle-pi@3.5.1 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. The `.git/gentle-ai/candidate-views` parent must sit on a filesystem that honors private POSIX modes (or equivalent Windows ACLs); WSL DrvFS mounts without metadata can reject START before lineage creation. -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.7.0`; raw prerelease binaries only under a prerelease pin). Windows x64 and arm64 build the exact `v3.7.0` 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. +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 `v4.0.0`; raw prerelease binaries only under a prerelease pin). Windows x64 and arm64 build the exact `v4.0.0` 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. Recommended companion packages, into the standalone `gentle-shell` home: @@ -517,7 +517,7 @@ flowchart TD 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. -For the source checkout, native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v3.7.0 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, and validate 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. +For the source checkout, native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v4.0.0 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, and validate 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. 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. @@ -527,7 +527,7 @@ Candidate views materialize tracked Git symlinks from their frozen blobs even wh On POSIX, if START rejects a group- or world-accessible `.git/gentle-ai/candidate-views` parent, Pi reports `candidate-owner-parent-privacy` before native START. When a sanitized probe shows the filesystem cannot represent private POSIX modes (for example WSL DrvFS mounts without `metadata`), Pi instead reports `candidate-owner-parent-chmod-ineffective` with guidance to move the Git common directory to a POSIX-metadata filesystem or enable metadata support. Inspect that parent's ownership and permissions and correct them out of band before retrying; Pi does not change them automatically. Other owner-preparation failures retain a generic diagnostic rather than exposing filesystem errors. -Once the source checkout's pinned gentle-ai runtime (currently v3.7.0) 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. +Once the source checkout's pinned gentle-ai runtime (currently v4.0.0) 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. ### FINALIZE wrapper input diff --git a/lib/native-review-cli.ts b/lib/native-review-cli.ts index 315284eb7..bc31d2ed6 100644 --- a/lib/native-review-cli.ts +++ b/lib/native-review-cli.ts @@ -1027,6 +1027,13 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({ // 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c // at contract 1.2.0. No new negotiated capability is asserted. "3.7.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 }), + // v4.0.0 repeats 3.7.0: the published provider-contract tar remains SHA-256 + // 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c + // at contract 1.2.0, and the published binary still advertises + // capabilities/v2.6 under review-integration/v2. The Go module path moved + // to /v4 without a review-integration/v2 change. No new negotiated + // capability is asserted. + "4.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 }), }); export interface NativeReviewProcessDiagnostics { diff --git a/lib/review-risk-assessment.ts b/lib/review-risk-assessment.ts index a9b2925ab..0f8b2b107 100644 --- a/lib/review-risk-assessment.ts +++ b/lib/review-risk-assessment.ts @@ -13,7 +13,7 @@ // gentle-pi#1175: the native v2 `assess.schema.json` requires only `code` on a // reason (`path`/`detail` are optional) and adds `candidate.consumed`, // `review_due`, `review_due_reason`, and an opaque `next_transition`. Older -// binaries (for example the pinned gentle-ai v3.7.0) predate those fields, so +// binaries (for example gentle-ai v3.7.0) predate those fields, so // they decode as optional and stay absent rather than being defaulted. // A non-zero exit or a failure envelope means the candidate could not be // assessed; hosts treat that as `high`. Older binaries without the verb (or diff --git a/odd/tasks/pin-gentle-ai-4.0.0-release.md b/odd/tasks/pin-gentle-ai-4.0.0-release.md new file mode 100644 index 000000000..f456e1359 --- /dev/null +++ b/odd/tasks/pin-gentle-ai-4.0.0-release.md @@ -0,0 +1,35 @@ +# Pin Gentle AI v4.0.0 and release Gentle Shell + +Repository-relative locator: `odd/tasks/pin-gentle-ai-4.0.0-release.md`. + +## Objective and rationale +Release Gentle Shell with the newly published Gentle AI v4.0.0 instead of its v3.7.0 pin, shipping the merged Pi 1.0.0 support (#1642). Approved issue: https://github.com/Gentleman-Programming/gentle-shell/issues/1643. Upstream: https://github.com/Gentleman-Programming/gentle-ai/releases/tag/v4.0.0. + +## Scope and constraints +- Branch `feat/pin-gentle-ai-4.0.0`, starting from `main` `afb45498`. +- Authorized: GitHub repositories Gentleman-Programming/gentle-ai (reads) and Gentleman-Programming/gentle-shell (issue, PR, merge, tag, release, `publish.yml`) through the configured `gh` CLI session over HTTPS. No ambient SSH, no local npm publication, no retagging. +- Pin only published signed v4.0.0 assets and the SumDB Windows source; regenerate derived runtime modules, never hand-edit generated files. +- Delivery: ask-on-risk. Forecast 200–300 authored lines for T1 (generated runtime excluded); single PR expected. + +## Verified upstream evidence (parent) +- `checksums.txt` verifies all four archives and the provider contract tarball. +- Archive SHA-256: darwin_amd64 `b5b74f22b38ec3339b38e8c68f797dc76ff12ed6580826a6e425d5c718da80c1`, darwin_arm64 `d2159caf6d68f367b18830ece6af71ef26963d5f5320d7df6a794773f45cc7e9`, linux_amd64 `5f4417cf29c969c86da4799942fd673368840901be1bb09c779a12d7ed6096ea`, linux_arm64 `1383b040c95cfc69206660d73c21907b14ad70ab913f410917c54f43d3147e57`. +- Extracted `gentle-ai` binary SHA-256: darwin_amd64 `d4a5b16ff70e65331e17a62356941bb0c75ecb9dbe3a0d98a6b54cfbd76cd6b0`, darwin_arm64 `18a9f7fae55d85c95684b6d512a4a148d0cb24a856325f72573c34caf65159eb`, linux_amd64 `50ba217b5138c1a9c7d5bf2f79931b1bb89b89c4cf650dcd7ee037657c88158d`, linux_arm64 `6703704f0c4a5b70c36fbdc44db641e810871cab16bc28040d06aa1d10704ad3`. +- Windows source: `go mod download -json github.com/gentleman-programming/gentle-ai/v4@v4.0.0` (GOSUMDB=sum.golang.org) → Sum `h1:pZ/XZ2Pk3U9lgXigOTY62zlxxFOHnc9CjQhLgaV/Hfc=`, tag commit `ff77164d4f56f1665b22fb6fac51c2ccbb769400`; `go.mod` declares `github.com/gentleman-programming/gentle-ai/v4`. +- Published linux_amd64 binary reports `gentle-ai 4.0.0`; `review capabilities` returns contract `gentle-ai.review-integration/v2`, schema `capabilities/v2.6` → no new capability identity. +- Provider contract tarball `gentle-ai-review-provider-contract-1.2.0.tar.gz` SHA-256 `547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c` (unchanged from 3.7.0). +- Contract diff v3.7.0..v4.0.0: review-integration v2 unchanged; v1 recover fixtures drop four fields; sdd-integration consent schema/fixture removed; telemetry adds `conductor` agent. + +## Tasks +- [ ] T1 — Pin published Gentle AI v4.0.0: installer version, archive/binary digests, Windows `/v4` module path + SumDB checksum, `NATIVE_CLI_CONTRACTS` 4.0.0 row, `/v4` paths in the ODD routing mirror script, regenerated runtime, pin-specific tests and docs. Route: delegated writer (multi-file write + preparation triggers). Risk: high (installer/process boundary) → writer self-checks + independent verifier. Acceptance: RED/GREEN observed; `pnpm test`, `check:runtime-modules`, `verify-package-files`, packed runner against real assets pass; work-unit commit. +- [ ] T2 — PR linked to #1643, merge after required checks, bump package version, tag from exact `main`, GitHub release with canonical notes, `publish.yml` from `main`, verify npm. Route: release coordination. + +## Progress +- Issue #1643 created with `enhancement`, `type:chore`, `status:approved` (read back). +- Release version decided by the user: gentle-pi 4.0.0 (mirrors the pinned Gentle AI major.minor, as 3.7.0 did). +- T1 delegated to one writer (route: delegated; multi-file write + preparation triggers). Writer completed: RED `published Gentle AI v4.0.0 is the installer pin` failed (`3.7.0` vs `4.0.0`), GREEN 1/1; focused 5 files 219 pass / 0 fail / 7 skip; `pnpm test` 4570 / 4526 pass / 0 fail / 44 skip (10 extra skips gate on a local `.gentle-ai/v4.0.0` binary not yet installed); runtime modules regenerated and match (8); `verify-package-files` passed (69 byte-pinned contracts); packed runner passed against real published v4.0.0 assets (gentle-pi 3.7.0 + Gentle AI 4.0.0); typecheck 187 / no regressions; diff check clean. 14 files +112/−88 incl. 2 generated runtime modules. +- Parent spot check: every archive/binary digest, SumDB Sum, tag commit and `/v4` module path appears exactly once in the installer; no `/v3` literal remains outside a historical comment; installer + contract tests 68/68 pass. +- Risk: high (installer/process boundary) → independent verifier runs after the T1 commit, including the local v4.0.0 binary install to remove the gated skips. + +## Next step +Delegate T1. diff --git a/runtime/native-review-cli.mjs b/runtime/native-review-cli.mjs index 5d1a78356..d27ba0b0b 100644 --- a/runtime/native-review-cli.mjs +++ b/runtime/native-review-cli.mjs @@ -1028,6 +1028,13 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({ // 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c // at contract 1.2.0. No new negotiated capability is asserted. "3.7.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 }), + // v4.0.0 repeats 3.7.0: the published provider-contract tar remains SHA-256 + // 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c + // at contract 1.2.0, and the published binary still advertises + // capabilities/v2.6 under review-integration/v2. The Go module path moved + // to /v4 without a review-integration/v2 change. No new negotiated + // capability is asserted. + "4.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 }), }); diff --git a/runtime/review-risk-assessment.mjs b/runtime/review-risk-assessment.mjs index b9c250337..eccd29557 100644 --- a/runtime/review-risk-assessment.mjs +++ b/runtime/review-risk-assessment.mjs @@ -14,7 +14,7 @@ // gentle-pi#1175: the native v2 `assess.schema.json` requires only `code` on a // reason (`path`/`detail` are optional) and adds `candidate.consumed`, // `review_due`, `review_due_reason`, and an opaque `next_transition`. Older -// binaries (for example the pinned gentle-ai v3.7.0) predate those fields, so +// binaries (for example gentle-ai v3.7.0) predate those fields, so // they decode as optional and stay absent rather than being defaulted. // A non-zero exit or a failure envelope means the candidate could not be // assessed; hosts treat that as `high`. Older binaries without the verb (or diff --git a/scripts/gentle-ai-installer.mjs b/scripts/gentle-ai-installer.mjs index c6b15ce14..97306c070 100644 --- a/scripts/gentle-ai-installer.mjs +++ b/scripts/gentle-ai-installer.mjs @@ -36,19 +36,20 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows"; // version check below) derives from this constant instead of repeating the // literal, so a pin bump cannot leave a stale copy behind. See // scripts/install-gentle-ai.mjs for the incident that motivated this. -export const INSTALLER_VERSION = "3.7.0"; +export const INSTALLER_VERSION = "4.0.0"; export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`; export const GENTLE_AI_INSTALL_METHOD = Object.freeze({ SIGNED_RELEASE_ASSET: "signed-release-asset", GO_SUMDB_SOURCE_BUILD: "go-sumdb-source-build", }); -export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai"; -export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v3"; +export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai"; +export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v4"; export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`; -// `go mod download -json github.com/gentleman-programming/gentle-ai/v3@v3.7.0` +// `go mod download -json github.com/gentleman-programming/gentle-ai/v4@v4.0.0` // with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the -// tag resolves to commit 6dee8f833aec9e46015759c5065a9035795d9af1, the published v3.7.0 release head. -export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:MQbzHlLdPklUQn0rVE9Mz94UygHsN2OPe7xMfPn9aGw="; +// tag resolves to commit ff77164d4f56f1665b22fb6fac51c2ccbb769400, the published v4.0.0 release head. +// v4.0.0 moved the Go module path to the /v4 major-version suffix. +export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:pZ/XZ2Pk3U9lgXigOTY62zlxxFOHnc9CjQhLgaV/Hfc="; export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`; export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10"; export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE"; @@ -67,7 +68,7 @@ export class GentleAiInstallerError extends Error { // Sentinel used while a re-pinned gentle-ai release is not yet published. A // sentinel digest can never match a real SHA-256, so installation fails closed, // and verify-package-files.mjs refuses to pack/publish while any digest below -// still holds it. The v3.7.0 digests are pinned from the published release: +// still holds it. The v4.0.0 digests are pinned from the published release: // archive sha256 values verified against the minisign-signed checksums.txt and // freshly computed hashes; binary sha256 values computed from the extracted // executables. @@ -109,15 +110,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) { } // Windows is absent from signed release archives on purpose. gentle-ai stopped -// distributing unsigned Windows builds in c4b764d0, so v3.7.0 publishes signed +// distributing unsigned Windows builds in c4b764d0, so v4.0.0 publishes signed // Darwin/Linux archives only. Windows x64/arm64 uses the separately verified // exact-tag Go SumDB source-build path below; restore archive rows only when // upstream ships signed Windows assets. export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({ - "darwin/amd64": asset("gentle-ai_3.7.0_darwin_amd64.tar.gz", "e55ff3ee06258a90e9195c0e3a593b85f6d79cc439e9e6c04b2596725120a610", "aa30a940e3b75ac218249b3f92d17afd2b972f833258e8212175a73d8e76d5bf", "gentle-ai"), - "darwin/arm64": asset("gentle-ai_3.7.0_darwin_arm64.tar.gz", "66eb5740c4506cb2cfd1cc5207439c61cfe07678854f4ac7c83027a95a0e666a", "ca726cf3f9a523dc0112aa113beb634cda389e8b47edc5851254c32979bee89e", "gentle-ai"), - "linux/amd64": asset("gentle-ai_3.7.0_linux_amd64.tar.gz", "a730a61a43758f04cc9a4ac644945cc0e8652a1e33d6997a0a3d3f0044d2fff5", "002d09fd2b9628a29986a660c1f51f8a5042ff7fd54c8ab15b7b27de96c6cccc", "gentle-ai"), - "linux/arm64": asset("gentle-ai_3.7.0_linux_arm64.tar.gz", "a3a3d3a974f3d9b67d935fe9e306ae83c305da4ec1baed4a5319c10b044cd0eb", "e29546c51d8d65528bf565239ed3fc174da73c5fa3e861e64f20f84b16f28f26", "gentle-ai"), + "darwin/amd64": asset("gentle-ai_4.0.0_darwin_amd64.tar.gz", "b5b74f22b38ec3339b38e8c68f797dc76ff12ed6580826a6e425d5c718da80c1", "d4a5b16ff70e65331e17a62356941bb0c75ecb9dbe3a0d98a6b54cfbd76cd6b0", "gentle-ai"), + "darwin/arm64": asset("gentle-ai_4.0.0_darwin_arm64.tar.gz", "d2159caf6d68f367b18830ece6af71ef26963d5f5320d7df6a794773f45cc7e9", "18a9f7fae55d85c95684b6d512a4a148d0cb24a856325f72573c34caf65159eb", "gentle-ai"), + "linux/amd64": asset("gentle-ai_4.0.0_linux_amd64.tar.gz", "5f4417cf29c969c86da4799942fd673368840901be1bb09c779a12d7ed6096ea", "50ba217b5138c1a9c7d5bf2f79931b1bb89b89c4cf650dcd7ee037657c88158d", "gentle-ai"), + "linux/arm64": asset("gentle-ai_4.0.0_linux_arm64.tar.gz", "1383b040c95cfc69206660d73c21907b14ad70ab913f410917c54f43d3147e57", "6703704f0c4a5b70c36fbdc44db641e810871cab16bc28040d06aa1d10704ad3", "gentle-ai"), }); // A pinned asset is either a signed archive or, for a prerelease pin only, diff --git a/scripts/mirror-odd-routing.mjs b/scripts/mirror-odd-routing.mjs index e7f9e0d62..0449a05f6 100644 --- a/scripts/mirror-odd-routing.mjs +++ b/scripts/mirror-odd-routing.mjs @@ -38,8 +38,8 @@ import ( "fmt" "os" - "github.com/gentleman-programming/gentle-ai/v3/internal/components/agentguidance" - "github.com/gentleman-programming/gentle-ai/v3/internal/model" + "github.com/gentleman-programming/gentle-ai/v4/internal/components/agentguidance" + "github.com/gentleman-programming/gentle-ai/v4/internal/model" ) func main() { diff --git a/scripts/verify-package-files.mjs b/scripts/verify-package-files.mjs index 4a81c7c3f..e1a726171 100644 --- a/scripts/verify-package-files.mjs +++ b/scripts/verify-package-files.mjs @@ -331,7 +331,7 @@ async function main() { }); if (driftedContracts.length > 0) { - console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v3.7.0 runtime's vendored Gentle AI contract artifacts:"); + console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v4.0.0 runtime's vendored Gentle AI contract artifacts:"); for (const drift of driftedContracts) console.error(`- ${drift.relativePath}: expected ${drift.expected}, got ${drift.actual}`); process.exit(1); } @@ -376,7 +376,7 @@ async function main() { process.exit(1); } - console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v3.7.0 runtime).`); + console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v4.0.0 runtime).`); } const isMainModule = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href; diff --git a/tests/gentle-ai-binary.test.ts b/tests/gentle-ai-binary.test.ts index de3d7a3fb..120d279a2 100644 --- a/tests/gentle-ai-binary.test.ts +++ b/tests/gentle-ai-binary.test.ts @@ -95,9 +95,9 @@ async function writeWindowsSourceBinary(packageRoot: string): Promise<{ binaryPa await writeFile(manifestPath, `${JSON.stringify({ version: GENTLE_AI_VERSION, method: "go-sumdb-source-build", - package: "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai", - module: "github.com/gentleman-programming/gentle-ai/v3", - tag: "v3.7.0", + package: "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai", + module: "github.com/gentleman-programming/gentle-ai/v4", + tag: "v4.0.0", architecture: process.arch === "x64" ? "x64" : "arm64", binarySha256: createHash("sha256").update(binary).digest("hex"), moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, diff --git a/tests/gentle-ai-installer.test.ts b/tests/gentle-ai-installer.test.ts index 9b3c0b1da..4e1a268d3 100644 --- a/tests/gentle-ai-installer.test.ts +++ b/tests/gentle-ai-installer.test.ts @@ -12,6 +12,7 @@ import { GENTLE_AI_PENDING_DIGEST, INSTALLER_VERSION, GENTLE_AI_RELEASE_ASSETS, + GENTLE_AI_WINDOWS_SOURCE_MODULE, GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, downloadGentleAiAsset, gentleAiAssetForm, @@ -21,18 +22,20 @@ import { trustedSystemExtractor, } from "../scripts/gentle-ai-installer.mjs"; -// v3.7.0 archive digests independently match the minisign-signed release +// v4.0.0 archive digests independently match the minisign-signed release // checksums; binary digests were computed from the extracted executables. const EXPECTED_ASSETS = { - "darwin/amd64": { name: "gentle-ai_3.7.0_darwin_amd64.tar.gz", sha256: "e55ff3ee06258a90e9195c0e3a593b85f6d79cc439e9e6c04b2596725120a610", binarySha256: "aa30a940e3b75ac218249b3f92d17afd2b972f833258e8212175a73d8e76d5bf" }, - "darwin/arm64": { name: "gentle-ai_3.7.0_darwin_arm64.tar.gz", sha256: "66eb5740c4506cb2cfd1cc5207439c61cfe07678854f4ac7c83027a95a0e666a", binarySha256: "ca726cf3f9a523dc0112aa113beb634cda389e8b47edc5851254c32979bee89e" }, - "linux/amd64": { name: "gentle-ai_3.7.0_linux_amd64.tar.gz", sha256: "a730a61a43758f04cc9a4ac644945cc0e8652a1e33d6997a0a3d3f0044d2fff5", binarySha256: "002d09fd2b9628a29986a660c1f51f8a5042ff7fd54c8ab15b7b27de96c6cccc" }, - "linux/arm64": { name: "gentle-ai_3.7.0_linux_arm64.tar.gz", sha256: "a3a3d3a974f3d9b67d935fe9e306ae83c305da4ec1baed4a5319c10b044cd0eb", binarySha256: "e29546c51d8d65528bf565239ed3fc174da73c5fa3e861e64f20f84b16f28f26" }, + "darwin/amd64": { name: "gentle-ai_4.0.0_darwin_amd64.tar.gz", sha256: "b5b74f22b38ec3339b38e8c68f797dc76ff12ed6580826a6e425d5c718da80c1", binarySha256: "d4a5b16ff70e65331e17a62356941bb0c75ecb9dbe3a0d98a6b54cfbd76cd6b0" }, + "darwin/arm64": { name: "gentle-ai_4.0.0_darwin_arm64.tar.gz", sha256: "d2159caf6d68f367b18830ece6af71ef26963d5f5320d7df6a794773f45cc7e9", binarySha256: "18a9f7fae55d85c95684b6d512a4a148d0cb24a856325f72573c34caf65159eb" }, + "linux/amd64": { name: "gentle-ai_4.0.0_linux_amd64.tar.gz", sha256: "5f4417cf29c969c86da4799942fd673368840901be1bb09c779a12d7ed6096ea", binarySha256: "50ba217b5138c1a9c7d5bf2f79931b1bb89b89c4cf650dcd7ee037657c88158d" }, + "linux/arm64": { name: "gentle-ai_4.0.0_linux_arm64.tar.gz", sha256: "1383b040c95cfc69206660d73c21907b14ad70ab913f410917c54f43d3147e57", binarySha256: "6703704f0c4a5b70c36fbdc44db641e810871cab16bc28040d06aa1d10704ad3" }, } as const; -test("published Gentle AI v3.7.0 is the installer pin", () => { - assert.equal(INSTALLER_VERSION, "3.7.0"); - assert.equal(GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, "h1:MQbzHlLdPklUQn0rVE9Mz94UygHsN2OPe7xMfPn9aGw="); +test("published Gentle AI v4.0.0 is the installer pin", () => { + assert.equal(INSTALLER_VERSION, "4.0.0"); + // v4.0.0 moved the Go module path to the /v4 major-version suffix. + assert.equal(GENTLE_AI_WINDOWS_SOURCE_MODULE, "github.com/gentleman-programming/gentle-ai/v4"); + assert.equal(GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, "h1:pZ/XZ2Pk3U9lgXigOTY62zlxxFOHnc9CjQhLgaV/Hfc="); }); test("default installer package root is the package containing scripts, not its parent", () => { @@ -43,15 +46,15 @@ test("default installer package root is the package containing scripts, not its assert.notEqual(resolveGentleAiInstallerPackageRoot(), dirname(expectedPackageRoot)); }); -test("release mapping selects only the supported official v3.7.0 assets and pinned digests", () => { +test("release mapping selects only the supported official v4.0.0 assets and pinned digests", () => { assert.deepEqual( Object.fromEntries(Object.entries(GENTLE_AI_RELEASE_ASSETS).map(([key, asset]) => [key, { name: asset.name, sha256: asset.sha256, binarySha256: asset.binarySha256 }])), EXPECTED_ASSETS, ); - assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_3.7.0_linux_amd64.tar.gz"); - assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_3.7.0_darwin_arm64.tar.gz"); + assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_4.0.0_linux_amd64.tar.gz"); + assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_4.0.0_darwin_arm64.tar.gz"); for (const asset of Object.values(GENTLE_AI_RELEASE_ASSETS)) { - assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v3\.7\.0\//); + assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v4\.0\.0\//); } }); @@ -61,7 +64,7 @@ test("raw release assets are admitted only under a prerelease pin", () => { assert.equal(gentleAiAssetForm("gentle-ai_2.5.0-rc.3_windows_amd64.exe", "2.5.0-rc.3"), "raw-binary"); // A raw binary under a stable pin means the pin itself is wrong: stable // releases publish signed archives only, so this fails closed pre-download. - assert.throws(() => gentleAiAssetForm("gentle-ai_3.7.0_linux_amd64", "3.7.0"), /only admitted for a prerelease pin/); + assert.throws(() => gentleAiAssetForm("gentle-ai_4.0.0_linux_amd64", "4.0.0"), /only admitted for a prerelease pin/); assert.throws(() => gentleAiAssetForm("gentle-ai.dmg", "2.5.0-rc.3"), /unsupported Gentle AI release asset form/); // The current stable pin admits every pinned asset row through the same // gate the installer uses at download time (default installerVersion @@ -88,7 +91,7 @@ test("release digests are all-or-none and install fails closed while any digest }), /checksum mismatch/, ); - assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.7.0", "gentle-ai")), false); + assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.0.0", "gentle-ai")), false); } }); @@ -116,8 +119,8 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) { let goExecutable = "go"; const metadata = [ "gentle-ai.exe: go1.25.10", - "\tpath\tgithub.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai", - `\tmod\tgithub.com/gentleman-programming/gentle-ai/v3\tv3.7.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`, + "\tpath\tgithub.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai", + `\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.0.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`, "\tbuild\t-buildmode=exe", "\tbuild\t-compiler=gc", "\tbuild\tCGO_ENABLED=0", `\tbuild\tGOARCH=${fixtureOptions.goArchitecture ?? "amd64"}`, "\tbuild\tGOOS=windows", ].join("\n"); const run = async (file: string, arguments_: string[], options: WindowsGoCall["options"]) => { @@ -135,7 +138,7 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) { return { stdout: "", stderr: "" }; } if (file === goExecutable && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" }; - if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 3.7.0\n", stderr: "" }; + if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 4.0.0\n", stderr: "" }; throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`); }; return { calls, run, setGoExecutable: (path: string) => { goExecutable = path; } }; @@ -151,7 +154,7 @@ test("win32 x64 and arm64 install the exact Go SumDB source tag without archive const result = await installGentleAi({ packageRoot, platform: "win32", arch, execFile: fixture.run, resolveGoExecutable: async () => goPath }); assert.equal(result.installed, true); assert.deepEqual(fixture.calls.filter((call) => call.file === goPath).map((call) => call.arguments_.slice(0, 2)), [ - ["version"], ["install", "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai@v3.7.0"], ["version", "-m"], + ["version"], ["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.0.0"], ["version", "-m"], ]); } }); @@ -170,7 +173,7 @@ test("Windows source install reports missing or too-old Go without publishing a () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }), (error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode, ); - assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.7.0", "gentle-ai.exe")), false); + assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.0.0", "gentle-ai.exe")), false); assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.includes("install-")), []); } }); @@ -189,7 +192,7 @@ test("Windows source install cleans staging after Go failure or wrong built vers () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }), (error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode, ); - const runtimeDirectory = join(packageRoot, ".gentle-ai", "v3.7.0"); + const runtimeDirectory = join(packageRoot, ".gentle-ai", "v4.0.0"); assert.ok(fixture.calls.some((call) => call.arguments_[0] === "install")); assert.equal(existsSync(join(runtimeDirectory, "gentle-ai.exe")), false); assert.equal(existsSync(runtimeDirectory) && (await readdir(runtimeDirectory)).some((entry) => entry.startsWith(".go-install-") || entry.endsWith(".tmp")), false); @@ -210,7 +213,7 @@ test("Windows source installs reuse only a fully verified package-local binary", assert.equal(reused.installed, false); assert.ok(reuse.calls.every((call) => call.file !== "gentle-ai"), "the installer must never fall back to ambient gentle-ai on PATH"); - await writeFile(join(packageRoot, ".gentle-ai", "v3.7.0", "integrity.json"), "{}\n"); + await writeFile(join(packageRoot, ".gentle-ai", "v4.0.0", "integrity.json"), "{}\n"); const repaired = windowsGoFixture(); repaired.setGoExecutable(goPath); assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: repaired.run, resolveGoExecutable: async () => goPath })).installed, true); @@ -251,8 +254,8 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har : undefined; const metadata = fixtureOptions.metadataOverride ?? [ "gentle-ai.exe: go1.25.10", - "\tpath\tgithub.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai", - `\tmod\tgithub.com/gentleman-programming/gentle-ai/v3\tv3.7.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`, + "\tpath\tgithub.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai", + `\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.0.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`, "\tbuild\t-buildmode=exe", "\tbuild\t-compiler=gc", "\tbuild\tCGO_ENABLED=0", @@ -272,7 +275,7 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har return { stdout: "", stderr: "" }; } if (file === goPath && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" }; - if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 3.7.0\n", stderr: "" }; + if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 4.0.0\n", stderr: "" }; throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`); }; return { @@ -314,7 +317,7 @@ test("Windows source installation resolves one validated Go executable and seals const goCalls = fixture.calls.filter((call) => call.file === fixture.goPath); assert.deepEqual(goCalls.map((call) => call.arguments_.slice(0, 2)), [ ["version"], - ["install", "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai@v3.7.0"], + ["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.0.0"], ["version", "-m"], ]); for (const call of goCalls) { @@ -340,14 +343,14 @@ test("Windows source manifest binds verified Go metadata and architecture", asyn const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-provenance-")); const fixture = await hardenedWindowsGoFixture(packageRoot, { architecture: "arm64" }); const result = await installGentleAi({ packageRoot, platform: "win32", arch: "arm64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }); - const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v3.7.0", "integrity.json"), "utf8")) as Record; + const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v4.0.0", "integrity.json"), "utf8")) as Record; assert.equal(result.installed, true); assert.deepEqual(manifest, { - version: "3.7.0", + version: "4.0.0", method: "go-sumdb-source-build", - package: "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai", - module: "github.com/gentleman-programming/gentle-ai/v3", - tag: "v3.7.0", + package: "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai", + module: "github.com/gentleman-programming/gentle-ai/v4", + tag: "v4.0.0", architecture: "arm64", binarySha256: createHash("sha256").update("trusted Windows source build").digest("hex"), moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, @@ -367,12 +370,12 @@ test("Windows source installation rejects Go metadata for a different architectu () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }), (error: unknown) => error instanceof Error && "code" in error && error.code === "GENTLE_AI_GO_INSTALL_FAILED", ); - assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.7.0", "gentle-ai.exe")), false); + assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.0.0", "gentle-ai.exe")), false); }); test("Windows source installation treats a fresh ownerless lock as active", async () => { const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-lock-")); - const lockPath = join(packageRoot, ".gentle-ai", ".v3.7.0.install.lock"); + const lockPath = join(packageRoot, ".gentle-ai", ".v4.0.0.install.lock"); await mkdir(lockPath, { recursive: true }); const fixture = await hardenedWindowsGoFixture(packageRoot); await assertManualLockRecoveryRequired(packageRoot, fixture); @@ -380,7 +383,7 @@ test("Windows source installation treats a fresh ownerless lock as active", asyn test("Windows source installation preserves a lock whose owner nonce changed before release", async () => { const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owner-lock-")); - const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v3.7.0.install.lock", "owner.json"); + const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v4.0.0.install.lock", "owner.json"); const fixture = await hardenedWindowsGoFixture(packageRoot); let replacedOwner = false; const run = async (...arguments_: Parameters) => { @@ -397,7 +400,7 @@ test("Windows source installation preserves a lock whose owner nonce changed bef }); async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Awaited>, options: Record = {}) { - const lockPath = join(packageRoot, ".gentle-ai", ".v3.7.0.install.lock"); + const lockPath = join(packageRoot, ".gentle-ai", ".v4.0.0.install.lock"); await assert.rejects( () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable, ...options }), (error: unknown) => error instanceof Error && error.message.includes(lockPath) && /confirm no installer is active.*remove.*manually/i.test(error.message), @@ -407,18 +410,18 @@ async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Aw } function tombstonePath(packageRoot: string, nonce: string): string { - return join(packageRoot, ".gentle-ai", `.v3.7.0.install.tombstone-${nonce}`); + return join(packageRoot, ".gentle-ai", `.v4.0.0.install.tombstone-${nonce}`); } async function tombstones(packageRoot: string): Promise { const runtimeRoot = join(packageRoot, ".gentle-ai"); return existsSync(runtimeRoot) - ? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v3.7.0.install.tombstone-")) + ? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v4.0.0.install.tombstone-")) : []; } function backupBundlePath(packageRoot: string, nonce: string): string { - return join(packageRoot, ".gentle-ai", `.v3.7.0.backup-${nonce}`); + return join(packageRoot, ".gentle-ai", `.v4.0.0.backup-${nonce}`); } async function copyWindowsBundle(source: string, destination: string): Promise { @@ -429,7 +432,7 @@ async function copyWindowsBundle(source: string, destination: string): Promise { const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-stale-lock-")); - const lockPath = join(packageRoot, ".gentle-ai", ".v3.7.0.install.lock"); + const lockPath = join(packageRoot, ".gentle-ai", ".v4.0.0.install.lock"); await mkdir(lockPath, { recursive: true }); const fixture = await hardenedWindowsGoFixture(packageRoot); await assertManualLockRecoveryRequired(packageRoot, fixture, { now: () => Date.now() + 10 * 60_000 }); @@ -437,7 +440,7 @@ test("Windows source installation fails closed for an ownerless lock even after test("Windows source installation fails closed for a stale owner lock", async () => { const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-stale-lock-")); - const lockPath = join(packageRoot, ".gentle-ai", ".v3.7.0.install.lock"); + const lockPath = join(packageRoot, ".gentle-ai", ".v4.0.0.install.lock"); await mkdir(lockPath, { recursive: true }); await writeFile(join(lockPath, "owner.json"), `${JSON.stringify({ createdAt: 0, nonce: "0".repeat(64) })}\n`); const fixture = await hardenedWindowsGoFixture(packageRoot); @@ -489,7 +492,7 @@ test("Windows source acquisition fails closed when a tombstone appears after its (error: unknown) => error instanceof Error && error.message.includes(foreignTombstone), ); assert.equal(existsSync(foreignTombstone), true); - assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v3.7.0.install.lock")), false); + assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.0.0.install.lock")), false); assert.equal(fixture.calls.some((call) => call.arguments_[0] === "install"), false); }); @@ -497,7 +500,7 @@ test("Windows source release deletes its matching tombstone and allows reuse", a const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owned-lock-release-")); const fixture = await hardenedWindowsGoFixture(packageRoot); await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }); - assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v3.7.0.install.lock")), false); + assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.0.0.install.lock")), false); assert.deepEqual(await tombstones(packageRoot), []); const reuse = await hardenedWindowsGoFixture(packageRoot); assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: reuse.run, resolveGoExecutable: reuse.resolveGoExecutable })).installed, false); @@ -507,7 +510,7 @@ test("Windows source recovers a valid backup after a crash between publication r const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-crash-")); const initial = await hardenedWindowsGoFixture(packageRoot); await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable }); - const live = join(packageRoot, ".gentle-ai", "v3.7.0"); + const live = join(packageRoot, ".gentle-ai", "v4.0.0"); const backup = backupBundlePath(packageRoot, "crash"); await rename(live, backup); const recovery = await hardenedWindowsGoFixture(packageRoot); @@ -543,7 +546,7 @@ test("Windows source cleans one validated backup only when the live bundle is va const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-valid-live-")); const initial = await hardenedWindowsGoFixture(packageRoot); await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable }); - const live = join(packageRoot, ".gentle-ai", "v3.7.0"); + const live = join(packageRoot, ".gentle-ai", "v4.0.0"); const backup = backupBundlePath(packageRoot, "valid"); await copyWindowsBundle(live, backup); const reuse = await hardenedWindowsGoFixture(packageRoot); @@ -556,7 +559,7 @@ test("Windows source preserves a valid backup when the live bundle is invalid", const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-invalid-live-")); const initial = await hardenedWindowsGoFixture(packageRoot); await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable }); - const live = join(packageRoot, ".gentle-ai", "v3.7.0"); + const live = join(packageRoot, ".gentle-ai", "v4.0.0"); const backup = backupBundlePath(packageRoot, "valid"); await copyWindowsBundle(live, backup); await writeFile(join(live, "integrity.json"), "{}\n"); @@ -574,7 +577,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t const fixture = await hardenedWindowsGoFixture(packageRoot, { blockInstall: true }); const first = installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }); await fixture.waitForInstall(); - const lockPath = join(packageRoot, ".gentle-ai", ".v3.7.0.install.lock"); + const lockPath = join(packageRoot, ".gentle-ai", ".v4.0.0.install.lock"); await assert.rejects( () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }), (error: unknown) => error instanceof Error && error.message.includes(lockPath), @@ -588,7 +591,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t test("Windows source publication rolls back a prior bundle when final directory swap fails", async () => { const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-rollback-")); - const versionDirectory = join(packageRoot, ".gentle-ai", "v3.7.0"); + const versionDirectory = join(packageRoot, ".gentle-ai", "v4.0.0"); await mkdir(versionDirectory, { recursive: true }); await writeFile(join(versionDirectory, "old.txt"), "previous bundle"); const fixture = await hardenedWindowsGoFixture(packageRoot); @@ -625,7 +628,7 @@ test("Darwin/Linux signed bundles retain their four-field manifest and reusable }, }; await installGentleAi(options); - const manifestPath = join(packageRoot, ".gentle-ai", "v3.7.0", "integrity.json"); + const manifestPath = join(packageRoot, ".gentle-ai", "v4.0.0", "integrity.json"); const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as Record; assert.deepEqual(Object.keys(manifest), ["version", "asset", "assetSha256", "binarySha256"]); assert.equal((await installGentleAi({ ...options, download: async () => { throw new Error("signed bundle must be reused"); } })).installed, false); @@ -638,7 +641,7 @@ test("a pinned asset falls back to its gentle-pi mirror only on download failure // under the current stable pin, whose form gate refuses raw binaries. const payload = Buffer.from("signed archive fixture"); const binary = "mirrored binary"; - const baseAsset = { name: "gentle-ai_3.7.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" }; + const baseAsset = { name: "gentle-ai_4.0.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" }; const extractArchive = async (_archive: string, destination: string) => { await mkdir(destination, { recursive: true }); await writeFile(join(destination, "gentle-ai"), binary); @@ -756,7 +759,7 @@ test("checksum mismatch cleans temporary state without promoting a binary", asyn }), /checksum mismatch/, ); - assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.7.0", "gentle-ai")), false); + assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.0.0", "gentle-ai")), false); assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.startsWith(".gentle-ai-install-")), []); }); @@ -778,7 +781,7 @@ test("installer promotes only the expected regular executable with executable PO await chmod(extracted, 0o700); }, }); - const binary = join(packageRoot, ".gentle-ai", "v3.7.0", "gentle-ai"); + const binary = join(packageRoot, ".gentle-ai", "v4.0.0", "gentle-ai"); assert.equal(existsSync(binary), true); assert.equal(await readFile(binary, "utf8"), "native executable"); assert.ok(((await stat(binary)).mode & 0o111) !== 0); @@ -809,7 +812,7 @@ test("installer rejects an extracted binary that differs from its pinned digest" }), /binary checksum mismatch/, ); - assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.7.0", "gentle-ai")), false); + assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.0.0", "gentle-ai")), false); }); test("installer repairs a valid non-executable POSIX binary instead of reusing it", async (t) => { @@ -832,7 +835,7 @@ test("installer repairs a valid non-executable POSIX binary instead of reusing i }, }; await installGentleAi(options); - const binary = join(packageRoot, ".gentle-ai", "v3.7.0", "gentle-ai"); + const binary = join(packageRoot, ".gentle-ai", "v4.0.0", "gentle-ai"); await chmod(binary, 0o600); const repaired = await installGentleAi(options); assert.equal(repaired.installed, true); @@ -869,7 +872,7 @@ test("installer rejects archives with multiple expected executable entries", asy }), /exactly one regular gentle-ai/, ); - assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.7.0", "gentle-ai")), false); + assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.0.0", "gentle-ai")), false); }); test("installer rejects an archive without the expected regular executable", async () => { @@ -887,7 +890,7 @@ test("installer rejects an archive without the expected regular executable", asy }), /non-regular gentle-ai/, ); - assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v3.7.0", "gentle-ai")), false); + assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.0.0", "gentle-ai")), false); }); test("safeRemoveDirectory passes Windows-safe retry options to recursive rm cleanup", async () => { diff --git a/tests/native-review-capability-contract.test.ts b/tests/native-review-capability-contract.test.ts index 62b8017fb..ba9961b13 100644 --- a/tests/native-review-capability-contract.test.ts +++ b/tests/native-review-capability-contract.test.ts @@ -367,11 +367,17 @@ test("3.7.0 explicitly repeats 3.6.1 because provider contract 1.2.0 is unchange assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["3.6.1"] as Record); }); +test("4.0.0 explicitly repeats 3.7.0 because provider contract 1.2.0 and capabilities/v2.6 are unchanged", () => { + const contract = NATIVE_CLI_CONTRACTS["4.0.0"] as Record; + assert.ok(contract); + assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["3.7.0"] as Record); +}); + test("no shipped version key was added beyond the pin bump", () => { // Rows are promises to consumers, so a new key only ever appears in a // dedicated commit alongside a pin bump, never as a side effect. v2.2.4 and // v2.3.0 shipped upstream while Pi stayed on 2.2.3 and were never pinned, // so they get no row: a row asserts ground truth measured against a binary // Pi actually ran, and the table only has to be ascending, not gapless. - 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", "3.1.0", "3.2.1", "3.4.0", "3.5.0", "3.6.0", "3.6.1", "3.7.0"]); + 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", "3.1.0", "3.2.1", "3.4.0", "3.5.0", "3.6.0", "3.6.1", "3.7.0", "4.0.0"]); }); diff --git a/tests/package-manifest.test.ts b/tests/package-manifest.test.ts index b1764b75f..f33f1ec7a 100644 --- a/tests/package-manifest.test.ts +++ b/tests/package-manifest.test.ts @@ -411,20 +411,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat ); }); -test("package verification binds the published Gentle AI v3.7.0 runtime pin", () => { +test("package verification binds the published Gentle AI v4.0.0 runtime pin", () => { const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8"); const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8"); const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8"); - assert.match(installer, /INSTALLER_VERSION = "3\.7\.0"/); + assert.match(installer, /INSTALLER_VERSION = "4\.0\.0"/); assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/); - assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:MQbzHlLdPklUQn0rVE9Mz94UygHsN2OPe7xMfPn9aGw="/); + assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:pZ\/XZ2Pk3U9lgXigOTY62zlxxFOHnc9CjQhLgaV\/Hfc="/); assert.match(installer, /GOTOOLCHAIN: "local"/); assert.match(installer, /GOSUMDB: "sum\.golang\.org"/); assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/); assert.match(binary, /GO_SUMDB_SOURCE_BUILD/); assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/); - assert.match(verifier, /v3\.7\.0/); + assert.match(verifier, /v4\.0\.0/); }); diff --git a/tests/review-risk-assessment.test.ts b/tests/review-risk-assessment.test.ts index 2d47febe3..e67a35472 100644 --- a/tests/review-risk-assessment.test.ts +++ b/tests/review-risk-assessment.test.ts @@ -92,8 +92,8 @@ test("decodeReviewAssessmentV1 rejects a malformed shape", () => { // --------------------------------------------------------------------------- // gentle-pi#1175: the native v2 `assess.schema.json` requires only `code` on a // reason, and adds `candidate.consumed`, `review_due`, `review_due_reason`, -// and `next_transition`. Older binaries (for example the pinned gentle-ai -// v3.7.0) predate those fields; they must decode without invented values. +// and `next_transition`. Older binaries (for example gentle-ai v3.7.0) +// predate those fields; they must decode without invented values. // --------------------------------------------------------------------------- function nextTransition(overrides: Record = {}): Record {