Skip to content

Commit d88d6fd

Browse files
authored
Gate Yield releases on package history (#25)
1 parent 19ccfe9 commit d88d6fd

3 files changed

Lines changed: 135 additions & 8 deletions

File tree

.github/workflows/private-registry.yml

Lines changed: 125 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,13 @@ jobs:
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
@@ -47,6 +52,8 @@ jobs:
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:
@@ -81,7 +88,16 @@ jobs:
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

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,5 @@ jobs:
9191
--repo "$GITHUB_REPOSITORY" \
9292
--title "$VERSION" \
9393
--generate-notes \
94+
--draft \
9495
--verify-tag

.github/workflows/verify.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ jobs:
3131
- run: go test ./...
3232
- run: go build ./...
3333

34+
packaging:
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v4
38+
- uses: actions/setup-node@v4
39+
with:
40+
node-version: "24"
41+
- run: node --test packaging/*.test.mjs
42+
3443
verify-sync-provenance:
3544
if: >-
3645
github.event_name == 'pull_request' &&

0 commit comments

Comments
 (0)