3333 - uses : actions/setup-go@v5
3434 with :
3535 go-version : stable
36+ - uses : actions/setup-node@v4
37+ with :
38+ node-version : " 24"
3639 - name : Verify the current release source
37- run : go test ./...
40+ run : |
41+ go test ./...
42+ node --test packaging/*.test.mjs
3843 - name : Check the one-package release contract
3944 run : |
4045 set -euo pipefail
4752 runs-on : ubuntu-latest
4853 outputs :
4954 version : ${{ steps.version.outputs.version }}
55+ previous_version : ${{ steps.version.outputs.previous_version }}
56+ source_sha : ${{ steps.version.outputs.source_sha }}
5057 steps :
5158 - uses : actions/checkout@v4
5259 with :
8188 [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
8289 git rev-parse --verify "refs/tags/v${version}"
8390 test "$(git rev-list -n 1 "v${version}")" = "$(git rev-parse HEAD)"
91+ previous="$(git tag --list 'v[0-9]*' --sort=-v:refname \
92+ | sed 's/^v//' \
93+ | awk -F. '$1 > 0 || $2 > 1 || ($2 == 1 && $3 >= 23)' \
94+ | awk -F. -v current="$version" '
95+ BEGIN { split(current, part, ".") }
96+ $1 < part[1] || ($1 == part[1] && $2 < part[2]) || ($1 == part[1] && $2 == part[2] && $3 < part[3]) { print; exit }
97+ ')"
8498 echo "version=$version" >> "$GITHUB_OUTPUT"
99+ echo "previous_version=$previous" >> "$GITHUB_OUTPUT"
100+ echo "source_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
85101
86102 - id : auth
87103 uses : google-github-actions/auth@v2
@@ -243,6 +259,28 @@ jobs:
243259 --repository="${{ vars.AR_GENERIC_REPO }}" --package=yield-rust --version="$VERSION" \
244260 --source-directory=dist/rust-public-publish --skip-existing
245261
262+ - name : Verify complete package history
263+ env :
264+ VERSION : ${{ steps.version.outputs.version }}
265+ run : |
266+ set -euo pipefail
267+ versions="$(git tag --list 'v[0-9]*' --sort=v:refname \
268+ | sed 's/^v//' \
269+ | awk -F. '$1 > 0 || $2 > 1 || ($2 == 1 && $3 >= 23)' \
270+ | awk -v current="$VERSION" '{ print; if ($0 == current) exit }' \
271+ | paste -sd, -)"
272+ test -n "$versions"
273+ for attempt in {1..12}; do
274+ if node packaging/verify-registry-history.mjs --versions "$versions"; then
275+ exit 0
276+ fi
277+ if [[ "$attempt" == 12 ]]; then
278+ echo "Package history was still incomplete after 12 checks." >&2
279+ exit 1
280+ fi
281+ sleep 10
282+ done
283+
246284 install-journeys :
247285 name : Install journeys (${{ matrix.target }})
248286 needs : publish
@@ -278,6 +316,7 @@ jobs:
278316 shell : bash
279317 env :
280318 VERSION : ${{ needs.publish.outputs.version }}
319+ PREVIOUS_VERSION : ${{ needs.publish.outputs.previous_version }}
281320 GOPROXY : https://get.operatorstack.systems/go,direct
282321 run : |
283322 set -euo pipefail
@@ -327,23 +366,30 @@ jobs:
327366 "$python_cmd" -m venv .venv
328367 if [[ "$RUNNER_OS" == "Windows" ]]; then python_bin=.venv/Scripts/python; else python_bin=.venv/bin/python; fi
329368 "$python_bin" -m pip install "yieldskill==${VERSION}" --index-url https://get.operatorstack.systems/pip/simple/
369+ if [[ "$RUNNER_OS" == "Windows" ]]; then
370+ printf '@echo off\r\necho venv-tool-ready\r\n' > .venv/Scripts/yield-venv-tool.cmd
371+ else
372+ printf '#!/bin/sh\necho venv-tool-ready\n' > .venv/bin/yield-venv-tool
373+ chmod +x .venv/bin/yield-venv-tool
374+ fi
330375 "$python_bin" -m yieldskill --version | grep -F "yskill ${VERSION}"
331376 "$python_bin" -m yieldskill init skill --language python --description "Run this workflow when checking the installed Python package."
332377 expect_incomplete_starter "$python_bin" -m yieldskill doctor skill --test
333378 finish_starter skill/main.py \
334379 ' ctx.blocked("replace the starter workflow and fixture before testing")' \
335- ' return {"installed": True}'
380+ $' python = ctx.run_command("venv-python", "python --version", 30)\n ctx.require(python.exit_code == 0, "the selected Python environment is on PATH", python)\n tool = ctx.run_command("venv-tool", "yield-venv-tool", 30)\n ctx.require(tool.exit_code == 0, "environment-local console scripts are on PATH", tool)\n return {"installed": True}'
336381 "$python_bin" -m yieldskill doctor skill --test
337382 "$python_bin" -m yieldskill register skill --agent cursor,codex,claude-code
338383 "$python_bin" -m yieldskill doctor skill --agent cursor,codex,claude-code
339384
340385 cd "$workspace/go"
341- git init -q
342386 mkdir -p .yield/bin
343387 GOBIN="$PWD/.yield/bin" go install "github.com/operatorstack/yield/cmd/yskill@v${VERSION}"
344- if [[ "$RUNNER_OS" == "Windows" ]]; then go_cli=.yield/bin/yskill.exe; else go_cli=.yield/bin/yskill; fi
388+ if [[ "$RUNNER_OS" == "Windows" ]]; then go_cli=.yield/bin/yskill.exe; go_launcher='.\.yield\bin\yskill.exe'; else go_cli=.yield/bin/yskill; go_launcher =.yield/bin/yskill; fi
345389 "$go_cli" --version | grep -F "yskill ${VERSION}"
346- "$go_cli" init skill --language go --description "Run this workflow when checking the installed Go package."
390+ "$go_cli" init skill --language go --description "Run this workflow when checking the installed Go package." | tee "$RUNNER_TEMP/go-init.log"
391+ grep -F "$go_launcher doctor 'skill' --test" "$RUNNER_TEMP/go-init.log"
392+ grep -F "$go_launcher run 'skill'" skill/SKILL.md
347393 expect_incomplete_starter "$go_cli" doctor skill --test
348394 finish_starter skill/main.go \
349395 'return yield.Outcome{}, ctx.Blocked("replace the starter workflow and fixture before testing")' \
@@ -353,15 +399,86 @@ jobs:
353399 "$go_cli" doctor skill --agent cursor,codex,claude-code
354400
355401 cd "$workspace/rust"
356- git init -q
357402 cargo install "yieldskill@${VERSION}" --index sparse+https://get.operatorstack.systems/cargo/index/ --locked --root .yield
358- if [[ "$RUNNER_OS" == "Windows" ]]; then rust_cli=.yield/bin/yskill.exe; else rust_cli=.yield/bin/yskill; fi
403+ if [[ "$RUNNER_OS" == "Windows" ]]; then rust_cli=.yield/bin/yskill.exe; rust_launcher='.\.yield\bin\yskill.exe'; else rust_cli=.yield/bin/yskill; rust_launcher =.yield/bin/yskill; fi
359404 "$rust_cli" --version | grep -F "yskill ${VERSION}"
360- "$rust_cli" init skill --language rust --description "Run this workflow when checking the installed Rust package."
405+ "$rust_cli" init skill --language rust --description "Run this workflow when checking the installed Rust package." | tee "$RUNNER_TEMP/rust-init.log"
406+ grep -F "$rust_launcher doctor 'skill' --test" "$RUNNER_TEMP/rust-init.log"
407+ grep -F "$rust_launcher run 'skill'" skill/SKILL.md
361408 expect_incomplete_starter "$rust_cli" doctor skill --test
362409 finish_starter skill/src/main.rs \
363410 ' Err(ctx.blocked("replace the starter workflow and fixture before testing"))' \
364411 ' Ok(json!({"installed": true}))'
365412 "$rust_cli" doctor skill --test
366413 "$rust_cli" register skill --agent cursor,codex,claude-code
367414 "$rust_cli" doctor skill --agent cursor,codex,claude-code
415+
416+ if [[ -n "$PREVIOUS_VERSION" ]]; then
417+ cd "$workspace"
418+ mkdir -p previous/typescript previous/python previous/go previous/rust
419+
420+ cd previous/typescript
421+ npm init -y >/dev/null
422+ npm install --save-exact "@operatorstack/yield@${PREVIOUS_VERSION}" --registry=https://get.operatorstack.systems/npm/
423+ npm exec -- yskill --version | grep -F "yskill ${PREVIOUS_VERSION}"
424+
425+ cd "$workspace/previous/python"
426+ "$python_cmd" -m venv .venv
427+ if [[ "$RUNNER_OS" == "Windows" ]]; then previous_python=.venv/Scripts/python; else previous_python=.venv/bin/python; fi
428+ "$previous_python" -m pip install "yieldskill==${PREVIOUS_VERSION}" --index-url https://get.operatorstack.systems/pip/simple/
429+ "$previous_python" -m yieldskill --version | grep -F "yskill ${PREVIOUS_VERSION}"
430+
431+ cd "$workspace/previous/go"
432+ mkdir -p .yield/bin
433+ GOBIN="$PWD/.yield/bin" go install "github.com/operatorstack/yield/cmd/yskill@v${PREVIOUS_VERSION}"
434+ if [[ "$RUNNER_OS" == "Windows" ]]; then previous_go=.yield/bin/yskill.exe; else previous_go=.yield/bin/yskill; fi
435+ "$previous_go" --version | grep -F "yskill ${PREVIOUS_VERSION}"
436+
437+ cd "$workspace/previous/rust"
438+ cargo install "yieldskill@${PREVIOUS_VERSION}" --index sparse+https://get.operatorstack.systems/cargo/index/ --locked --root .yield
439+ if [[ "$RUNNER_OS" == "Windows" ]]; then previous_rust=.yield/bin/yskill.exe; else previous_rust=.yield/bin/yskill; fi
440+ "$previous_rust" --version | grep -F "yskill ${PREVIOUS_VERSION}"
441+ fi
442+
443+ finalize-release :
444+ name : Publish verified release receipt
445+ needs : [publish, install-journeys]
446+ runs-on : ubuntu-latest
447+ permissions :
448+ contents : write
449+ steps :
450+ - name : Attach package contract and publish the draft
451+ env :
452+ GH_TOKEN : ${{ github.token }}
453+ VERSION : ${{ needs.publish.outputs.version }}
454+ PREVIOUS_VERSION : ${{ needs.publish.outputs.previous_version }}
455+ SOURCE_SHA : ${{ needs.publish.outputs.source_sha }}
456+ run : |
457+ set -euo pipefail
458+ if ! gh release view "v${VERSION}" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
459+ gh release create "v${VERSION}" \
460+ --repo "$GITHUB_REPOSITORY" \
461+ --title "v${VERSION}" \
462+ --generate-notes \
463+ --draft \
464+ --verify-tag
465+ fi
466+ jq -n \
467+ --arg version "$VERSION" \
468+ --arg previous_version "$PREVIOUS_VERSION" \
469+ --arg source_sha "$SOURCE_SHA" \
470+ --arg repository "$GITHUB_REPOSITORY" \
471+ '{
472+ schema_version: 1,
473+ version: $version,
474+ previous_version: $previous_version,
475+ source_sha: $source_sha,
476+ repository: $repository,
477+ languages: ["typescript", "python", "go", "rust"],
478+ targets: ["linux-amd64", "linux-arm64", "darwin-amd64", "darwin-arm64", "windows-amd64", "windows-arm64"],
479+ package_history_from: "0.1.23",
480+ current_and_previous_install_journeys: true,
481+ package_history_complete: true
482+ }' > yield-package-contract.json
483+ gh release upload "v${VERSION}" yield-package-contract.json --repo "$GITHUB_REPOSITORY" --clobber
484+ gh release edit "v${VERSION}" --repo "$GITHUB_REPOSITORY" --draft=false --latest
0 commit comments