diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4a06450..3bce2c7 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -40,12 +40,14 @@ jobs: - uses: DeterminateSystems/determinate-nix-action@021c8a1bd3570eb21f5c20a054812b0c4d9ca614 # v3 - - name: Build and smoke-test the opencode package + - name: Build and smoke-test the V1 and V2 packages run: | set -euo pipefail - nix build .#opencode --print-build-logs - version="$(nix eval --raw .#opencode.version)" - ./result/bin/opencode --version | grep -F "$version" + for package in opencode opencode-v2; do + nix build ".#$package" --print-build-logs + version="$(nix eval --raw ".#$package.version")" + ./result/bin/opencode --version | grep -Fx -e "$version" -e "opencode v$version" + done notify-ci: needs: [check, package-build, flakehub-publish, flakehub-verify] diff --git a/Justfile b/Justfile index 6fbccc3..d0232cd 100644 --- a/Justfile +++ b/Justfile @@ -23,3 +23,13 @@ update: # Pin the packaged opencode to a specific version update-to VERSION: ./scripts/update-opencode.sh --version {{VERSION}} + +# V2 is an independent official npm binary lane; V1 recipes stay unchanged. +update-v2-check: + ./scripts/update-opencode-v2.sh --check + +update-v2: + ./scripts/update-opencode-v2.sh + +update-v2-to VERSION: + ./scripts/update-opencode-v2.sh --version {{quote(VERSION)}} diff --git a/README.md b/README.md index 04902c6..515d3b7 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,16 @@ PRs. This repository's required CI builds and smoke-tests the package before merge. Merges to main are tagged `opencode-vX.Y.Z` and published to FlakeHub as rolling releases. +### V2 binary lane + +`packages.opencode-v2` / `homeManagerModules.withPackageV2` select the official +`@opencode/cli-linux-x64` binary; V1 remains the default. Use +`just update-v2-check`, `just update-v2`, or `just update-v2-to 2.X.Y` to check +or update its npm version and fixed-output integrity hash without building. +The required `package-build` job builds and checks both V1 and V2 versions; +`checks.updater-v2-mutation-only` tests V2 updater isolation and metadata validation. +Publish the leaf and pin its revision before activating a consumer. + ## Trust modes - **Rolling** (FlakeHub wildcard / `main`): every commit that passed the diff --git a/checks.nix b/checks.nix index ece2291..d84ad84 100644 --- a/checks.nix +++ b/checks.nix @@ -140,6 +140,14 @@ in { "nix MCP registered despite mcpNixos.enable = false"; pkgs.runCommand "opencode-defaults-posture" {} "touch $out"; + updater-v2-mutation-only = + pkgs.runCommand "opencode-v2-updater-mutation-only" { + nativeBuildInputs = [pkgs.bash pkgs.coreutils pkgs.gnugrep pkgs.gnused pkgs.jq pkgs.just]; + } '' + bash ${self}/tests/opencode-v2-contract.sh + touch "$out" + ''; + updater-mutation-only = pkgs.runCommand "opencode-updater-mutation-only" {} '' cp -r ${self} source chmod -R u+w source diff --git a/docs/behaviors/opencode-v2-migration/behavior.md b/docs/behaviors/opencode-v2-migration/behavior.md new file mode 100644 index 0000000..3af10fa --- /dev/null +++ b/docs/behaviors/opencode-v2-migration/behavior.md @@ -0,0 +1,3 @@ +lets migrate backends to v2. Lets also check if everything is working through our flake. Lets update packages and plugins lets explore context fold and usage sidebar. Lets also explore keyboard shortcuts for multiple session managing + +lets also apply all proposed improvements diff --git a/docs/behaviors/opencode-v2-migration/migration.feature b/docs/behaviors/opencode-v2-migration/migration.feature new file mode 100644 index 0000000..522f759 --- /dev/null +++ b/docs/behaviors/opencode-v2-migration/migration.feature @@ -0,0 +1,36 @@ +# Behavior contract; runnable bindings and observed results are recorded separately. +Feature: Managed OpenCode V2 workspace backends + Scenario: Independent native workspace backends + Given the homelab and dataplatform workspaces have preserved V1 databases + When the verified Endeavour user environment is activated + Then both explicitly addressed workspace backends report the pinned V2 version + And native ol and ow clients connect to their respective backends + And each backend uses a separate V2 database and its intended provider route + And neither original V1 database content or schema is modified by V2 + And the homelab legacy database receives only the approved owner-only permission change + + Scenario: Verified essential integrations + Given the exact V2 package and pinned plugins are installed through the flake + When a disposable model and shell roundtrip is performed + Then provider-scoped session headers reach only their intended providers + And RTK executes without weakening approval of the original shell command + And configured MCP servers connect and web fetch returns source content + + Scenario: Enforceable guardrails + Given native V2 permission prompts remain enabled + When an agent requests secret files or unapproved shell operations + Then protected direct reads are denied + And shell commands outside narrow inspection allowances require approval + And documented authorized deployment commands are not permanently denied + + Scenario: Optional context and quota experiments + Given Context Fold and usage sidebar have immutable enable-time pins + When the default managed environment starts + Then neither optional plugin loads + And no quota fallback is represented as custom gateway usage + + Scenario: Native session management + Given the global V2 CLI configuration is managed declaratively + When the operator manages multiple sessions + Then native session and tab shortcuts remain available + And terminal-specific delivery limitations are documented without claiming unobserved UI success diff --git a/docs/behaviors/opencode-v2-migration/stage-one-pager.md b/docs/behaviors/opencode-v2-migration/stage-one-pager.md new file mode 100644 index 0000000..0fbb52c --- /dev/null +++ b/docs/behaviors/opencode-v2-migration/stage-one-pager.md @@ -0,0 +1,46 @@ +# OpenCode V2 migration: planning and delivery + +**Status:** leaf package reviewed and locally GREEN; CI/publication and consumer integration remain. No activation. +**Stage / revision:** RV / 5, 2026-10-13. +**Basis:** [verbatim human seed](behavior.md), [behavior contract](migration.feature). + +## Outcome and why + +Both existing Endeavour workspace backends will use native V2 clients with isolated V2 databases. Old V1 database contents and schema remain untouched; history import is separate. Current Desktop origin/main already includes V2 packaging selection and safe RTK/gateway hooks. Earlier inspection of an older canonical branch must not be used as implementation baseline. + +## Decisions and boundaries + +- Q-1 accepted: native V2 backends and ol/ow launchers; no Rust/V1 client pairing. +- Q-2 accepted: Context Fold and usage sidebar remain opt-in and absent from default plugin lists. +- Q-3 accepted: Endeavour activation through the documented recipe after green checks. +- Q-4 accepted: publish reviewed green commits/task branches and leaf-first PRs in both repositories. Merging remains separately authorized. Consumer deployment waits for a published updated leaf. +- Q-5 accepted: restrict existing homelab V1 database from mode 0644 to 0600 through declarative activation. This explicit permission-only exception does not authorize reading or migrating its contents. +- Apply all six improvements: managed package, essential plugins, global CLI/compaction, V2 checks, permissions, pinned MCP/context cleanup. +- Only OpenCode-related dependencies are in scope. No fleet-wide upgrade, history import or additional orchestration framework. + +## Delivery slices and verification + +1. Leaf package: update official Linux-x64 V2 binary/hash; extend updater and required CI coverage. First observe assertion-failing tests for missing V2 coverage, then run those tests and build the exact V2 package. Preserve the V1 package lane. +2. Workspace routing: restore two explicit loopback V2 units with new database paths and native explicit-server launchers. Extend evaluated configuration tests first; then verify units, ports, provider routing, argument forwarding and preserved PATH behavior. +3. Settings and integrations: manage one global cli.json, supported compaction, update compatible pinned plugins and Donsetch, enforce secret-read/shell guardrails, remove redundant MCP overrides and duplicated startup guidance. Use existing header/credential tests and smallest necessary negative/positive checks. +4. Runtime: disposable exact-package header/tool/MCP smoke checks, then independent review and owner lint, format and dry-build gates. After a published immutable leaf pin exists, activate only Endeavour and query both explicit endpoints; do not use auto-discovery as evidence. + +Success requires zero exit plus actual assertions. Infrastructure errors are not RED; static evaluation is not E2E. Desktop project checks observed assertion failures for unpinned MCP and unrestricted shell before changes; all five current checks pass, including rejection of compound commands and secret-reading Git arguments. The opt-in fragment check is metadata validation, not plugin runtime. Each failure hypothesis gets at most three repair attempts. + +Leaf RED observed missing V2 updater/check/required-CI coverage, then additional malformed-metadata and exact-version failures. `bash tests/opencode-v2-contract.sh` passes with `PASS: V2 updater isolation, metadata validation, checks and required CI coverage`. Targeted updater checks and the exact V2 package build pass; `--version` reports `opencode v2.0.27`. The first remote build failed on missing acl-2.4.0; local retry succeeded. V1 remains 1.19.0 and full flake checks are reserved for CI. + +Desktop backend configuration/wrapper tests pass 27 cases, including approved permission-only legacy DB fixtures. A disposable exact-2.0.27 API smoke confirms its own PID, native explicit-server client attachment, authenticated health and unauthenticated 401. One test harness repair handled the CLI version prefix; another closed the negative-control HTTP response. No production backend was changed. Essential plugin/tool and final owner gates are still outstanding. + +Fresh leaf review reproduced one P2 finding: an unquoted Just recipe version could execute shell syntax and mask the updater failure. The added recipe-level regression failed before the fix, then passed with `quote(VERSION)`. Both targeted Nix lint and sandboxed updater checks pass after the fix. Review confirmed unchanged V1 behavior, sequential required CI coverage for both packages and exact registry integrity. The first-draft patch receipt is retained outside the repository; no acceptance or RED evidence was backdated. + +On 2026-10-13, exact 2.0.27 also completed a synthetic model → native RTK shell → model continuation. Desktop settings/project checks and all four touched-host dry-builds pass. Native plugin/permission checks and Ponytail request injection fail, so consumer deployment is blocked despite static success. The bounded repair loop stopped without weakening assertions. Optional runtime trials were cancelled and remain unverified. See the [reconciled test contract](test-contract.md) for per-scenario evidence boundaries. + +## Risks and recovery + +The independent grill identified publication, database namespaces, server-side routing, approval order and controlling-session continuity as mandatory boundaries. Keep original V1 databases and a reachable matching recovery generation. Do not change live configuration manually, use an unpublished sibling tree for production, or restart the controlling trial session. Restore only task-owned experiments. + +Separate clean task worktrees preserve unrelated work. Pending tmux PATH and isolated-trial MCP work must be reconciled before activation; their original worktrees stay unchanged. Global XDG variables from this harness point at trial roots, so documented HM activation needs explicit normal-user XDG roots. + +## Continuing work + +Reviewed green PR publication is authorized; leaf merge still needs approval. Publication is a real dependency for deploying the updated leaf, not a new activation approval gate. The leaf first-draft review is reconciled; consumer review and exact integration gates continue. Runtime results and unresolved gates remain explicit until observed. The Gherkin contract is traceability, not a claim that a feature runner executed. diff --git a/docs/behaviors/opencode-v2-migration/test-contract.md b/docs/behaviors/opencode-v2-migration/test-contract.md new file mode 100644 index 0000000..97310f3 --- /dev/null +++ b/docs/behaviors/opencode-v2-migration/test-contract.md @@ -0,0 +1,69 @@ +# OpenCode V2 migration: reconciled test contract + +**Status:** final traceability draft, 2026-10-13; reconciles accepted planning with delivered checks, not retrospective planning. + +**Basis:** [human seed](behavior.md), [accepted scenarios](migration.feature), [delivery evidence](stage-one-pager.md). Gherkin has no executable step bindings; checks cover specific assertions, not whole-scenario completion. + +Commands run from the indicated repository. `$V2` identifies the realized leaf 2.0.27 executable; `$PONYTAIL` identifies the realized managed Ponytail package directory. + +## Scenario bindings + +### Independent native workspace backends + +Desktop: + +```sh +python3 -m unittest discover -s tests/local-workspace-sessions -p 'test_*.py' +"$V2" --version +python3 tests/opencode-project/smoke_auth.py "$V2" 2.0.27 +``` + +Reported 27 tests cover evaluated units, isolated database/XDG paths, routing, launcher forwarding, PATH and synthetic chmod fixtures. Fixtures preserve inode/size/mtime and work-database metadata; they do not establish production V1 byte/schema preservation. + +Disposable auth smoke establishes exact version/PID, explicit native API addressing, authenticated `/api/info` and unauthenticated 401. Neither check activates production backends. + +### Verified essential integrations + +Leaf: + +```sh +bash tests/opencode-v2-contract.sh +nix build .#opencode-v2 --no-link +``` + +Recorded GREEN covers updater isolation, malformed metadata, required CI wiring and exact package build; CI execution remains separate evidence. + +Desktop: + +```sh +node tests/opencode-profiles/test_headers.mjs +node tests/opencode-profiles/test_plugins.mjs "$PONYTAIL" +OPENCODE_TEST_BINARY="$V2" python3 tests/opencode-profiles/test_header_integration.py +OPENCODE_TEST_BINARY="$V2" python3 tests/opencode-profiles/test_runtime_v2.py +OPENCODE_TEST_BINARY="$V2" python3 -m unittest discover -s tests/local-workspace-sessions -p test_tool_roundtrip.py +``` + +Hook checks cover provider filters, original RTK permission input and Ponytail transforms. The exact-version synthetic tool roundtrip passed with real RTK execution and model continuation. Loopback header test deliberately returns HTTP 400, but its Ponytail-injection assertion currently fails. Runtime API checks plugin activation and permission decisions; these currently fail. Latest results belong in the delivery record. + +### Enforceable guardrails + +```sh +python3 tests/opencode-profiles/test_settings.py +python3 tests/opencode-project/test_configuration.py +``` + +Settings/project checks inspect rules. Runtime checks exercise native read denial despite agent override, shell approval, narrow inspection allowances and destructive denial. Shell approval is not sandboxing. + +### Optional context and quota experiments + +Settings/project checks verify default absence, immutable fragments, Fold debug disabled and sidebar `default: none`. Optional runtime trials were cancelled; compatibility, quota retrieval and savings remain unverified. + +### Native session management + +Settings checks verify global CLI, prompt permissions, native tabs and unchanged default bindings. Desktop's `docs/guides/opencode-v2-sessions.md` documents shortcuts. Actual keyboard delivery remains manual/unobserved. + +## Delivery boundaries + +Q1–Q5 persist: native isolation; optional plugins off; Endeavour activation after GREEN; reviewed green commit/push/PR authorized, merges not; existing homelab V1 chmod-only 0600 exception. Run owner lint/fmt/docs/dry gates. Obtain separately authorized leaf merge/publication and immutable consumer pin before activation. Never substitute sibling source for production. + +Real-provider completion, MCP connectivity/source return and two live endpoint acceptance remain required deployment evidence, not established by static or loopback checks. Consumer's leaf remains 2.0.21 until publication. Full flake checks belong in CI. diff --git a/packages/opencode-v2/package.nix b/packages/opencode-v2/package.nix index 819cd1f..25694a7 100644 --- a/packages/opencode-v2/package.nix +++ b/packages/opencode-v2/package.nix @@ -14,7 +14,7 @@ versionCheckHook, writableTmpDirAsHomeHook, }: let - version = "2.0.21"; + version = "2.0.27"; in stdenvNoCC.mkDerivation { pname = "opencode-v2"; @@ -22,7 +22,7 @@ in src = fetchurl { url = "https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-${version}.tgz"; - hash = "sha512-intPGPRrEoZk+5Qq2dzYcq3Wv07vWvQrv56WieurupI9jGJSEAfT4a9Pi6YMW4tEW6SapAds/95K0egVS2YwiA=="; + hash = "sha512-QPGrzBSSs5ATG3nM4XjS4tmWXUB22Di36H0vfMuhKwk6i3PR3Nb4sm+l00gvTdcyCRohYsKwqjvD67T0DPcQLQ=="; }; nativeBuildInputs = [autoPatchelfHook]; diff --git a/scripts/update-opencode-v2.sh b/scripts/update-opencode-v2.sh new file mode 100755 index 0000000..a38c093 --- /dev/null +++ b/scripts/update-opencode-v2.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { printf '[opencode-v2-update] %s\n' "$*" >&2; } +die() { log "ERROR: $*"; exit 2; } + +check_only=false +target="" +while [ "$#" -gt 0 ]; do + case "$1" in + --check) check_only=true; shift ;; + --version) + [ "$#" -ge 2 ] || die '--version requires a version' + target="$2" + [[ "$target" =~ ^2\.[0-9]+\.[0-9]+$ ]] || die 'expected a stable V2 version' + shift 2 ;; + --help) + printf 'Usage: scripts/update-opencode-v2.sh [--check] [--version 2.X.Y]\n' + exit 0 ;; + *) die "unknown option: $1" ;; + esac +done + +readonly package='packages/opencode-v2/package.nix' +[ -f flake.nix ] && [ -f "$package" ] || die 'run from opencode-flake root' +for command in curl jq sed; do + command -v "$command" >/dev/null 2>&1 || die "$command is required" +done + +current="$(sed -n 's/^ version = "\([^"]*\)";/\1/p' "$package")" +current_hash="$(sed -n 's/^ hash = "\([^"]*\)";/\1/p' "$package")" +[[ "$current" =~ ^2\.[0-9]+\.[0-9]+$ ]] && [ -n "$current_hash" ] || die 'malformed package version/hash' +metadata="$(curl -fsSL "https://registry.npmjs.org/@opencode%2fcli-linux-x64/${target:-latest}")" || die 'npm metadata fetch failed' +validated="$(printf '%s\n' "$metadata" | jq -ser --arg target "$target" ' + select(length == 1) | .[0] | select(type == "object") + | select(.name == "@opencode/cli-linux-x64") + | select(.version | type == "string" and test("^2\\.[0-9]+\\.[0-9]+$")) + | select($target == "" or .version == $target) + | select(.dist.integrity | type == "string" and test("^sha512-[A-Za-z0-9+/]{85}[AQgw]==$")) + | select(.dist.tarball == ("https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-" + .version + ".tgz")) + | [.version, .dist.integrity] | @tsv +')" || die 'malformed npm package metadata' +IFS=$'\t' read -r latest hash <<< "$validated" + +log "current: $current" +log "latest: $latest" +if [ "$current" = "$latest" ] && [ "$current_hash" = "$hash" ]; then + log 'already up to date' + exit 0 +fi +if [ "$check_only" = true ]; then + log "update available: $current -> $latest" + exit 1 +fi + +# npm supplies the fixed-output SRI hash; updating never builds or runs Nix. +sed -i \ + -e "s|^ version = \"[^\"]*\";| version = \"$latest\";|" \ + -e "s|^ hash = \"[^\"]*\";| hash = \"$hash\";|" \ + "$package" +log "updated $package" diff --git a/tests/opencode-v2-contract.sh b/tests/opencode-v2-contract.sh new file mode 100644 index 0000000..abbc51a --- /dev/null +++ b/tests/opencode-v2-contract.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; } + +# Keep V2 inside the existing required CI gate, without replacing V1. +missing=0 +for contract in \ + 'scripts/update-opencode-v2.sh|' \ + 'Justfile|update-v2-check:' \ + 'Justfile|update-v2:' \ + 'Justfile|update-v2-to VERSION:' \ + 'checks.nix|updater-v2-mutation-only =' \ + '.github/workflows/check.yml|for package in opencode opencode-v2; do'; do + file="${contract%%|*}" + pattern="${contract#*|}" + if ! test -f "$root/$file" || ! grep -Fq "$pattern" "$root/$file"; then + printf 'FAIL: missing V2 contract: %s\n' "$contract" >&2 + missing=1 + fi +done +test "$missing" = 0 || exit 1 +grep -Fq 'tests/opencode-v2-contract.sh' "$root/checks.nix" || fail 'V2 test not registered' +# shellcheck disable=SC2016 # Match literal workflow expressions. +grep -Fq 'nix build ".#$package"' "$root/.github/workflows/check.yml" || fail 'CI must build both packages' +# shellcheck disable=SC2016 +grep -Fq 'grep -Fx -e "$version" -e "opencode v$version"' "$root/.github/workflows/check.yml" || fail 'CI must assert exact V1/V2 version output' + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT +mkdir -p "$tmp/source/packages/opencode" "$tmp/source/packages/opencode-v2" "$tmp/source/scripts" "$tmp/bin" +cp "$root/Justfile" "$tmp/source/" +cp "$root/scripts/update-opencode-v2.sh" "$tmp/source/scripts/" +cp "$root/flake.nix" "$tmp/source/" +cp "$root/packages/opencode/package.nix" "$tmp/source/packages/opencode/" +cp "$root/packages/opencode-v2/package.nix" "$tmp/source/packages/opencode-v2/" +cp "$root/scripts/update-opencode-v2.sh" "$tmp/source/update.sh" +cp "$tmp/source/packages/opencode/package.nix" "$tmp/v1-before" +cd "$tmp/source" + +cat > "$tmp/bin/curl" <<'SH' +#!/usr/bin/env bash +printf '%s\n' "$*" > "$TEST_REQUEST" +test "${TEST_CURL_FAIL:-0}" = 0 || exit 22 +cat "$TEST_METADATA" +SH +cat > "$tmp/bin/nix" <<'SH' +#!/usr/bin/env bash +printf 'unexpected nix invocation\n' >&2 +touch "$TEST_NIX_CALLED" +exit 125 +SH +sed -i "1c#!$(command -v bash)" "$tmp/bin/curl" "$tmp/bin/nix" +cp "$tmp/bin/nix" "$tmp/bin/nix-update" +chmod +x "$tmp/bin/"* +export PATH="$tmp/bin:$PATH" +export TEST_METADATA="$tmp/metadata.json" TEST_REQUEST="$tmp/request" TEST_NIX_CALLED="$tmp/nix-called" + +integrity='sha512-QPGrzBSSs5ATG3nM4XjS4tmWXUB22Di36H0vfMuhKwk6i3PR3Nb4sm+l00gvTdcyCRohYsKwqjvD67T0DPcQLQ==' +jq -n --arg hash "$integrity" '{name:"@opencode/cli-linux-x64",version:"2.99.0",dist:{integrity:$hash,tarball:"https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-2.99.0.tgz"}}' > "$TEST_METADATA" + +expect_status() { + local expected="$1" status=0 + shift + bash ./update.sh "$@" > "$tmp/output" 2>&1 || status=$? + if test "$status" != "$expected"; then + cat "$tmp/output" >&2 + fail "updater $*: exit $status, expected $expected" + fi +} + +cp packages/opencode-v2/package.nix "$tmp/v2-before" +status=0 +just update-v2-to 'bad; printf V2_RECIPE_INJECTION' > "$tmp/recipe-output" 2>&1 || status=$? +test "$status" != 0 || fail 'recipe masked invalid-version failure with shell injection' +! grep -Fxq 'V2_RECIPE_INJECTION' "$tmp/recipe-output" || fail 'recipe executed version as shell syntax' +cmp packages/opencode-v2/package.nix "$tmp/v2-before" || fail 'invalid recipe argument mutated V2' +expect_status 1 --check +cmp packages/opencode-v2/package.nix "$tmp/v2-before" || fail '--check mutated V2' +grep -Fq 'https://registry.npmjs.org/@opencode%2fcli-linux-x64/latest' "$TEST_REQUEST" || fail 'wrong latest endpoint' +expect_status 0 --version 2.99.0 +grep -Fq 'version = "2.99.0";' packages/opencode-v2/package.nix || fail 'version not updated' +grep -Fq "hash = \"$integrity\";" packages/opencode-v2/package.nix || fail 'integrity not updated' +grep -Fq 'https://registry.npmjs.org/@opencode%2fcli-linux-x64/2.99.0' "$TEST_REQUEST" || fail 'wrong version endpoint' +sed -e 's/version = "2.99.0";/version = "TEST";/' -e 's/hash = "[^"]*";/hash = "TEST";/' packages/opencode-v2/package.nix > "$tmp/after-normalized" +sed -e 's/version = "[^"]*";/version = "TEST";/' -e 's/hash = "[^"]*";/hash = "TEST";/' "$tmp/v2-before" > "$tmp/before-normalized" +cmp "$tmp/before-normalized" "$tmp/after-normalized" || fail 'updated more than version/hash' +expect_status 0 --check +cp "$TEST_METADATA" "$tmp/valid.json" +cp packages/opencode-v2/package.nix "$tmp/v2-updated" + +for filter in '.version = null' '.version = "1.99.0"' '.version = "2.99.1"' '.name = "wrong-package"' '.dist.integrity = "sha512-invalid"' '.dist.tarball = "https://example.com/package.tgz"'; do + jq "$filter" "$tmp/valid.json" > "$TEST_METADATA" + expect_status 2 --version 2.99.0 + cmp packages/opencode-v2/package.nix "$tmp/v2-updated" || fail 'invalid metadata mutated V2' +done +printf 'not json\n' > "$TEST_METADATA" +expect_status 2 --version 2.99.0 +cat "$tmp/valid.json" "$tmp/valid.json" > "$TEST_METADATA" +expect_status 2 --version 2.99.0 +expect_status 2 --version +expect_status 2 --version 1.99.0 +export TEST_CURL_FAIL=1 +expect_status 2 --check +cmp packages/opencode-v2/package.nix "$tmp/v2-updated" || fail 'failed fetch mutated V2' +cmp packages/opencode/package.nix "$tmp/v1-before" || fail 'V1 package changed' +test ! -e "$TEST_NIX_CALLED" || fail 'updater invoked nix or nix-update' +printf 'PASS: V2 updater isolation, metadata validation, checks and required CI coverage\n'