QVAC-22630 chore: release @qvac/model-fit 0.6.0 #13447
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Checks (SDK Pod) | |
| on: | |
| # Plain `pull_request` (NOT pull_request_target): fork PRs run with a | |
| # read-only token and no secrets, so it is safe to check out and execute PR | |
| # code (install/lint/build/test). GITHUB_TOKEN (packages: read) resolves | |
| # @tetherto/* GPR mono packages on same-repo PRs; fork PRs may still fail | |
| # to install private GPR deps (acceptable — coordinated inference work is | |
| # internal same-repo PRs). | |
| # | |
| # No trigger-level `paths:` filter on purpose: this workflow publishes the | |
| # required "SDK Pod Checks" status, which must report on EVERY PR or GitHub | |
| # blocks unrelated PRs ("waiting for status to be reported"). The single job | |
| # below detects SDK pod changes itself and is a fast no-op when nothing | |
| # relevant changed. | |
| pull_request: | |
| types: | |
| - opened | |
| - synchronize | |
| - reopened | |
| - labeled | |
| branches: | |
| - main | |
| - release-* | |
| - feature-* | |
| - tmp-* | |
| workflow_dispatch: | |
| # Lets pr-gate-merge.yml call this workflow directly. | |
| workflow_call: | |
| permissions: | |
| contents: read | |
| # Package config lives in .github/sdk-pod-checks.json | |
| # Scripts (format, lint, typecheck, build, test:unit) are auto-detected from each package's package.json | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: true | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Single required status check for SDK pod packages. | |
| # | |
| # Runs on every PR so the "SDK Pod Checks" context is always reported — a | |
| # required check that is never reported blocks the PR forever. It is a fast | |
| # no-op (detect step only) when no SDK pod files changed, so unrelated PRs are | |
| # unaffected. Mark THIS job's name ("SDK Pod Checks") required in the ruleset. | |
| # | |
| # - no SDK pod files changed -> pass (no checks run) | |
| # - changed + checks pass -> pass | |
| # - changed + a check fails -> fail (blocks the merge) | |
| # - changed + a check fails, but the 'skip-sdk-pod-checks' label is set | |
| # -> pass (audited override for e.g. a | |
| # confirmed false-positive test; the | |
| # failure is still run and logged) | |
| # | |
| # The package config and the diff baseline are read from the trusted base | |
| # commit (not the PR), so a PR cannot edit the config to skip its own checks. | |
| # Packages are checked sequentially in this single job (instead of a parallel | |
| # matrix) so that exactly one check appears on every PR. | |
| # | |
| # First-add exemption: a package's config entry is read from base, so the PR | |
| # that introduces a package is not gated by this job — its entry is absent | |
| # from base, the package filters out, and the job passes without running its | |
| # checks. This follows from config-from-base being the anti-tampering | |
| # property: it necessarily exempts the introducing PR. Such a PR is gated by a | |
| # local run of these checks; once its entry is on the base branch, every | |
| # later PR touching the package is gated here. | |
| # --------------------------------------------------------------------------- | |
| sdk-pod-checks: | |
| name: SDK Pod Checks | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: read | |
| # Hard cap so a hung job can't hog runners or burn minutes. Slightly above | |
| # the 30-min default because this rollup runs up to 6 packages sequentially | |
| # (incl. the SDK build + bare/e2e tests and the two consumer installs). | |
| timeout-minutes: 45 | |
| steps: | |
| # Checkout the trusted base commit. The package config and the diff | |
| # baseline are read from here — never from the PR — so a PR cannot edit | |
| # the config to exclude itself from its own checks. | |
| - name: Checkout base | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha || github.ref }} | |
| fetch-depth: 0 | |
| - name: Detect changed SDK pod packages | |
| id: detect | |
| timeout-minutes: 5 | |
| shell: bash | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| run: | | |
| # Fail closed: any error here (git fetch/diff, jq) fails the step and | |
| # therefore the whole "SDK Pod Checks" job — never a silent green. | |
| set -euo pipefail | |
| # Default optional fields. `sources` is which SDK variants to test a | |
| # package against: sdk_sources_release on release-* PRs (and dispatch), | |
| # else sdk_sources. Both default to ["default"] — one leg, no prep. | |
| ALL_PACKAGES=$(jq -c --arg event "$EVENT" --arg base "${BASE_REF:-}" ' | |
| [ .[] | |
| | .pkg_manager //= "npm" | |
| | .needs_bare //= false | |
| | .tests_bare //= false | |
| | .sdk_sources //= ["default"] | |
| | .sdk_sources_release //= .sdk_sources | |
| | .sources = (if ($event == "workflow_dispatch" or ($base | startswith("release-"))) | |
| then .sdk_sources_release else .sdk_sources end) | |
| ]' .github/sdk-pod-checks.json) | |
| if [ "$EVENT" = "workflow_dispatch" ]; then | |
| # workflow_dispatch: run all packages | |
| PACKAGES="$ALL_PACKAGES" | |
| else | |
| # Fetch PR head (available on the repo via pull refs) | |
| git fetch origin "refs/pull/${PR_NUMBER}/head" | |
| # Filter to packages whose own path or a `depends_on` path changed. | |
| CHANGED_FILES=$(git diff --name-only "$BASE_SHA"..."$HEAD_SHA") | |
| PACKAGES=$(echo "$ALL_PACKAGES" | jq -c --arg files "$CHANGED_FILES" ' | |
| ($files | split("\n")) as $changed | |
| | [ .[] | |
| | ([.path] + (.depends_on // [])) as $paths | |
| | select(any($paths[]; . as $p | any($changed[]; startswith($p + "/")))) | |
| ]') | |
| fi | |
| echo "packages=${PACKAGES}" >> "$GITHUB_OUTPUT" | |
| has_changes=$(echo "$PACKAGES" | jq -r 'if length > 0 then "true" else "false" end') | |
| echo "has_changes=${has_changes}" >> "$GITHUB_OUTPUT" | |
| if [ "$has_changes" = "true" ]; then | |
| echo "::notice::SDK pod packages changed: $(echo "$PACKAGES" | jq -r '[.[].package] | join(", ")')" | |
| else | |
| echo "::notice::No SDK pod package files changed - gate passes." | |
| fi | |
| # ----- Everything below runs only when SDK pod files changed ----- | |
| # Safe under `pull_request`: fork PRs get a read-only token and no secrets, | |
| # so checking out and executing PR code cannot exfiltrate anything. | |
| - name: Checkout PR head | |
| if: steps.detect.outputs.has_changes == 'true' | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha || github.ref }} | |
| - name: Setup Bun | |
| if: steps.detect.outputs.has_changes == 'true' | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # 2.2.0 | |
| with: | |
| bun-version: latest | |
| - name: Setup Node | |
| if: steps.detect.outputs.has_changes == 'true' | |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # 6.3.0 | |
| with: | |
| node-version: 22 | |
| - name: Configure npm registries | |
| if: steps.detect.outputs.has_changes == 'true' | |
| shell: node {0} | |
| env: | |
| GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| if (!process.env.GPR_TOKEN) { | |
| console.log('::error::GITHUB_TOKEN is empty — cannot read @tetherto GPR packages'); | |
| process.exit(1); | |
| } | |
| const lines = [ | |
| 'registry=https://registry.npmjs.org/', | |
| '@qvac:registry=https://registry.npmjs.org/', | |
| '@tetherto:registry=https://npm.pkg.github.com/', | |
| `//npm.pkg.github.com/:_authToken=${process.env.GPR_TOKEN}`, | |
| ]; | |
| const npmrc = lines.join('\n') + '\n'; | |
| const target = path.join(process.env.GITHUB_WORKSPACE, '.npmrc'); | |
| fs.writeFileSync(target, npmrc, 'utf8'); | |
| console.log(`::notice::.npmrc configured at ${target} (@tetherto → GPR via GITHUB_TOKEN)`); | |
| - name: Run SDK pod checks | |
| if: steps.detect.outputs.has_changes == 'true' | |
| timeout-minutes: 40 | |
| shell: bash | |
| env: | |
| PACKAGES: ${{ steps.detect.outputs.packages }} | |
| # Maintainer escape hatch: when this label is present the gate still | |
| # runs and logs failures, but reports success so an urgent fix can | |
| # land over a confirmed false-positive check. Audited via the label | |
| # and the warning below. | |
| OVERRIDE: ${{ contains(github.event.pull_request.labels.*.name, 'skip-sdk-pod-checks') }} | |
| run: | | |
| set -uo pipefail | |
| WS="$GITHUB_WORKSPACE" | |
| fail=0 | |
| # Run a single check; record (don't abort on) failures so every check runs. | |
| run() { | |
| local label="$1" | |
| shift | |
| if "$@"; then | |
| echo " ok [$PKG] $label" | |
| else | |
| echo "::error::[$PKG] $label failed" | |
| fail=$((fail + 1)) | |
| fi | |
| } | |
| # Validates the SDK tarball installs cleanly for end consumers: | |
| # 1. zero peer-dependency warnings on install | |
| # 2. shared P2P packages resolve to a single copy | |
| # 3. import('@qvac/sdk') resolves from a vanilla install | |
| # Runs in a subshell so its directory changes do not leak. | |
| consumer_install_check() ( | |
| set -eo pipefail | |
| cd "$WS/packages/sdk" | |
| npmrc_src="$WS/.npmrc" | |
| # Pack the tarball produced by the preceding build step. | |
| bun pm pack --destination dist/ | |
| tarball=$(ls dist/qvac-sdk-*.tgz | head -n1) | |
| test -n "$tarball" || { echo "::error::No SDK tarball found after pack"; exit 1; } | |
| tarball_abs="$(pwd)/$tarball" | |
| # Pack the in-repo @qvac/inference so each consumer below redirects | |
| # the SDK's transitive @qvac/inference to it via an npm `overrides` | |
| # entry — sdk and inference ship lockstep, so the consumer resolves | |
| # the engine at this commit rather than a published release. | |
| inference_tgz="" | |
| if [ -n "$(jq -r '.dependencies["@qvac/inference"] // empty' package.json)" ]; then | |
| echo "::group::Pack in-repo @qvac/inference for consumer install" | |
| ( cd "$WS/packages/inference" && bun install && bun pm pack --destination dist/ ) | |
| inference_tgz=$(ls "$WS"/packages/inference/dist/qvac-inference-*.tgz | head -n1) | |
| test -n "$inference_tgz" || { echo "::error::No @qvac/inference tarball found after pack"; exit 1; } | |
| echo "::notice::Consumer installs override @qvac/inference -> $inference_tgz" | |
| echo "::endgroup::" | |
| fi | |
| check_consumer() { | |
| local consumer="$1" label="$2" | |
| cd "$consumer" | |
| if grep -Eq "ERESOLVE|npm warn peer" install.log; then | |
| echo "::error title=Peer dependency drift (${label})::SDK consumer install surfaced peer warnings" | |
| grep -E "ERESOLVE|npm warn peer" install.log || true | |
| return 1 | |
| fi | |
| echo "::notice::[${label}] 0 peer warnings" | |
| local f=0 tree copies | |
| for pkg in corestore hyperswarm hyperdrive hyperdb hyperblobs hyperdht; do | |
| tree=$(npm ls "$pkg" --all 2>&1) | |
| copies=$(printf '%s\n' "$tree" | grep -E "[─ ]${pkg}@" | grep -vc "deduped" || true) | |
| if [ "$copies" != "1" ]; then | |
| echo "::error::[${label}] ${pkg} resolved to ${copies} copies (expected 1)" | |
| printf '%s\n' "$tree" | |
| f=1 | |
| else | |
| echo " ok [${label}] ${pkg} = 1 copy" | |
| fi | |
| done | |
| [ "$f" = "0" ] || return 1 | |
| echo "::notice::[${label}] Single-copy invariant holds for shared P2P packages" | |
| node -e "import('@qvac/sdk').then(m => { if (Object.keys(m).length < 50) { console.error('FAIL: too few exports'); process.exit(1); } console.log('[${label}] import ok:', Object.keys(m).length, 'exports'); }).catch(e => { console.error('[${label}] FAIL:', e.message); process.exit(1); })" | |
| } | |
| # Scenario 1: default install (plug-n-play) - all optionalDependencies present. | |
| consumer_default=$(mktemp -d) | |
| cd "$consumer_default" | |
| npm init -y > /dev/null | |
| npm pkg set type=module > /dev/null | |
| if [ -f "$npmrc_src" ]; then cp "$npmrc_src" .npmrc; fi | |
| if [ -n "$inference_tgz" ]; then npm pkg set "overrides.@qvac/inference=file:$inference_tgz"; fi | |
| npm install --no-fund --no-audit --ignore-scripts --loglevel=info "$tarball_abs" 2>&1 | tee install.log | |
| check_consumer "$consumer_default" "default" | |
| # Scenario 2: lean backend install (--omit=optional) - no optionalDependencies. | |
| consumer_lean=$(mktemp -d) | |
| cd "$consumer_lean" | |
| npm init -y > /dev/null | |
| npm pkg set type=module > /dev/null | |
| if [ -f "$npmrc_src" ]; then cp "$npmrc_src" .npmrc; fi | |
| if [ -n "$inference_tgz" ]; then npm pkg set "overrides.@qvac/inference=file:$inference_tgz"; fi | |
| npm install --no-fund --no-audit --ignore-scripts --omit=optional --loglevel=info "$tarball_abs" 2>&1 | tee install.log | |
| check_consumer "$consumer_lean" "lean (--omit=optional)" | |
| ) | |
| # Install the Bare runtime once if any changed package needs it. | |
| if echo "$PACKAGES" | jq -e 'any(.[]; .needs_bare == true)' > /dev/null; then | |
| echo "::group::Install Bare runtime" | |
| npm install -g --force bare | |
| echo "::endgroup::" | |
| fi | |
| len=$(echo "$PACKAGES" | jq 'length') | |
| i=0 | |
| while [ "$i" -lt "$len" ]; do | |
| PKG=$(echo "$PACKAGES" | jq -r ".[$i].package") | |
| P_PATH=$(echo "$PACKAGES" | jq -r ".[$i].path") | |
| PM=$(echo "$PACKAGES" | jq -r ".[$i].pkg_manager") | |
| TESTS_BARE=$(echo "$PACKAGES" | jq -r ".[$i].tests_bare") | |
| SOURCES=$(echo "$PACKAGES" | jq -r ".[$i].sources[]") | |
| i=$((i + 1)) | |
| echo "::group::SDK pod checks - $PKG" | |
| cd "$WS/$P_PATH" | |
| if [ -f "$WS/.npmrc" ]; then cp "$WS/.npmrc" .npmrc; fi | |
| # Disallowed dependencies: no git URLs or dev/tmp versions on public | |
| # npm deps. @tetherto/* GPR mono prerelease pins (e.g. inference-mono | |
| # 0.16.0-tmp.runid-*) are allowed — coordinated SDK+inference PRs | |
| # consume them from GPR before a public npm release. | |
| while IFS=$'\t' read -r dep_name dep_value; do | |
| [ -n "$dep_name" ] || continue | |
| if echo "$dep_value" | grep -Eq '^git\+https://github.com'; then | |
| echo "::error::[$PKG] disallowed dependency detected (git URL): ${dep_name}@${dep_value}" | |
| fail=$((fail + 1)) | |
| continue | |
| fi | |
| if echo "$dep_name" | grep -Eq '^@tetherto/'; then | |
| continue | |
| fi | |
| if echo "$dep_value" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-(dev|tmp)'; then | |
| echo "::error::[$PKG] disallowed dependency detected (dev/tmp version): ${dep_name}@${dep_value}" | |
| fail=$((fail + 1)) | |
| fi | |
| done < <(jq -r '([.dependencies, .devDependencies] | map(select(type=="object")) | add // {}) | to_entries[] | "\(.key)\t\(.value)"' package.json) | |
| # Run the checks once per declared SDK source. The optional | |
| # sdk-source:<source> script does that source's setup (e.g. link the | |
| # in-repo SDK); absent = plain install of the committed dependency. | |
| src_idx=0 | |
| for SRC in $SOURCES; do | |
| # Bracketed source shown after each check name, e.g. "build [workspace]". | |
| # Empty for the lone "default" source (single-leg packages stay clean). | |
| src="" | |
| if [ "$SRC" != "default" ]; then src="[$SRC]"; fi | |
| # Reset to the committed manifest. For the 2nd+ source also wipe | |
| # node_modules + lockfile — a prior `npm install ../sdk` leaves a | |
| # symlink npm would keep, leaking that source into this one. | |
| git checkout HEAD -- package.json 2>/dev/null || true | |
| if [ "$src_idx" -gt 0 ]; then | |
| rm -rf node_modules package-lock.json | |
| fi | |
| # npm run --if-present reads package.json scripts and works regardless | |
| # of the installer (bun lacks --if-present). | |
| if [ "$PKG" = "sdk" ]; then | |
| run "install $src" bun install | |
| else | |
| run "install $src" "$PM" install | |
| fi | |
| # Per-source setup (no-op if the script is absent). | |
| run "sdk-source $src" npm run --if-present "sdk-source:$SRC" | |
| run "format $src" npm run --if-present format | |
| run "lint $src" npm run --if-present lint | |
| run "typecheck $src" npm run --if-present typecheck | |
| run "build:types $src" npm run --if-present build:types | |
| run "build $src" npm run --if-present build | |
| run "test:unit $src" npm run --if-present test:unit | |
| if [ "$TESTS_BARE" = "true" ]; then | |
| run "test:bare $src" npm run --if-present test:bare | |
| fi | |
| run "test:e2e $src" npm run --if-present test:e2e | |
| src_idx=$((src_idx + 1)) | |
| done | |
| # Back to the committed manifest for the sdk-only checks below. | |
| git checkout HEAD -- package.json 2>/dev/null || true | |
| if [ "$PKG" = "sdk" ]; then | |
| # Assert the committed Python-client contract (contract/schema.json, | |
| # contract/manifest.json) matches a fresh export. A dedicated named | |
| # check so a schema/registry change without regenerating fails with | |
| # a clear "contract:check failed" instead of being buried inside the | |
| # test:unit run above. | |
| run "contract:check" npm run --if-present contract:check | |
| run "resource collector packaging" npm run --if-present check:resource-collectors -- \ | |
| --host darwin-arm64 \ | |
| --host darwin-x64 \ | |
| --host linux-arm64 \ | |
| --host linux-x64 \ | |
| --host win32-arm64 \ | |
| --host win32-x64 | |
| if consumer_install_check; then | |
| echo " ok [$PKG] consumer install" | |
| else | |
| echo "::error::[$PKG] consumer install check failed" | |
| fail=$((fail + 1)) | |
| fi | |
| fi | |
| cd "$WS" | |
| echo "::endgroup::" | |
| done | |
| if [ "$fail" -gt 0 ]; then | |
| if [ "$OVERRIDE" = "true" ]; then | |
| echo "::warning::$fail SDK pod check(s) failed, but the 'skip-sdk-pod-checks' label is applied - overriding to allow this merge (e.g. a confirmed false-positive test). Fix the failing check in a follow-up." | |
| exit 0 | |
| fi | |
| echo "::error::There were $fail failed SDK pod check(s)" | |
| exit 1 | |
| fi | |
| echo "All SDK pod checks passed." |