Skip to main content

sdk-publish

Manually publish an already-tagged, already-released (non-draft GitHub Release) RunAnywhere SDK version to every package registry (npm, Maven Central, pub.dev, Swift SPM dist repo) plus macOS rcli notarization, with heavy verification that nothing fake/stubbed/broken ships.

소스 정보

저장소
RunanywhereAI/runanywhere-sdks
최근 소스 활동
2026년 8월 31일 01:26
감지된 SKILL.md 언어
영어
스타
10,318
포크
385

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
sdk-publish
description
Manually publish an already-tagged, already-released (non-draft GitHub Release) RunAnywhere SDK version to every package registry (npm, Maven Central, pub.dev, Swift SPM dist repo) plus macOS rcli notarization, with heavy verification that nothing fake/stubbed/broken ships.
# SDK Publish Repo: `~/development/ODLM/MONOREPOOO/Qualcomm/runanywhere-sdks` (verify with `pwd`/`git remote get-url origin` — do not assume this path is still correct, checkouts move). Version source of truth: `core/VERSION`. Throughout, `$VERSION` means the bare semver being published (e.g. `0.20.24`), never prefixed with `v` except in git tags / GitHub Release names. ## THIS PROCESS IS INTENTIONALLY MANUAL **`release.yml` never publishes anywhere public.** It only builds artifacts, packages them, and creates a draft/published **GitHub Release**. It does not call `npm publish`, does not upload to Maven Central, does not call `flutter pub publish`, and does not push the Swift dist repo. Verify this is still true before trusting it — greps below should all return **zero** matches (grep the whole file, not a snippet): ```bash cd ~/development/ODLM/MONOREPOOO/Qualcomm/runanywhere-sdks grep -inE "npm publish|publishReleasePublication|publishAllPublicationsToMavenCentral|flutter pub publish" .github/workflows/release.yml ``` This is a **deliberate safety gate**, not a gap. Every step below is a human (or an agent acting on a human's explicit go-ahead) pushing a button that CI intentionally never pushes. Do not propose wiring any of this into CI as a "fix" — that would remove the gate. ## Precondition check (always run first) Refuse to proceed if the release is a draft or the asset set looks incomplete. ```bash gh release view v$VERSION --json isDraft,tagName --jq '{isDraft, tagName}' # isDraft MUST be false. gh release view v$VERSION --json assets --jq '.assets[].name' | sort ``` Cross-reference the listing against the `sdk-release` skill's step 8 asset-count expectations (`~/.claude/skills/sdk-release/SKILL.md`). As a second, independent check, also diff against the **previous** tagged release's asset list — same count, same name patterns, only the version token differs: ```bash gh release view v<PREVIOUS_VERSION> --json assets --jq '.assets[].name' | sed "s/<PREVIOUS_VERSION>/$VERSION/" | sort >/tmp/expected.txt gh release view v$VERSION --json assets --jq '.assets[].name' | sort >/tmp/actual.txt diff /tmp/expected.txt /tmp/actual.txt ``` As of the last verified run (**v0.20.25, 65 assets** — up from 55 at v0.20.24 because the 5 public Electron tarballs and their sidecars joined the release; see §6), the full set is: `MANIFEST.txt`; per-platform `RACommons-{android-arm64-v8a,android-armeabi-v7a,android-x86_64,ios,linux-x86_64,web,windows-x64}-v$VERSION.{zip,tar.gz}` + `.sha256`; iOS backend zips `RABackend{LLAMACPP,MLX,NeuRT,ONNX,Sherpa}-ios-v$VERSION.zip` + `.sha256`; `RunAnywhereMLX{Metal,Resources,Runtime}-ios-v$VERSION.zip` + `.sha256`; `rcli-{linux-x86_64,macos-arm64,windows-x86_64}-v$VERSION.tar.gz`/`.zip` + `.sha256`; the 8 npm tarballs `runanywhere-{core,llamacpp,mlx,onnx,proto-ts,web,web-llamacpp,web-onnx}-$VERSION.tgz` + `.sha256`; the **5 Electron npm tarballs** `runanywhere-electron{,-llamacpp,-onnx,-sherpa,-neurt}-$VERSION.tgz` + `.sha256` (new as of v0.20.25); and `runanywhere-kotlin-maven-v$VERSION.zip` + `.sha256`. There is **no** Flutter tarball in the release (Flutter packages are staged from the native archives above, not from a dedicated release asset) and **no** `.dmg` asset in the baseline set (the macOS rcli tarball is what ships; see the notarization section for when a `.dmg` does get produced). If a `qhexrt`-named asset appears anywhere in this list, stop — that would mean `release.yml`'s own manifest assertion should have hard-failed and something is very wrong; do not publish anything from that release. ## 0. MANDATORY GATE — verify the ENTIRE release before pushing anything to any registry **Run this once, in full, before the first registry push. Do not publish a single package until it passes.** Per-package spot-checks are not a substitute: they are how a bad artifact reaches a registry you cannot un-publish from. Proven end to end on v0.20.25 (2026-08-22), then independently audited and corrected: **34 payloads, 6619 files across 3 nesting levels, 282 binaries, 0 placeholder hits, 38/38 commons binaries carrying the real staging host**. The audit found 4 methodology gaps in the first pass (shallow nesting, missing `MTLB` magic, `.lib` excluded from the positive check, and a set-intersection that could pass vacuously) — every one is called out inline below, because each produced a *confident but under-covered* result. ```bash VERSION=0.20.25 # no leading v REPO=RunanywhereAI/runanywhere-sdks W=/tmp/relverify-$VERSION rm -rf "$W" && mkdir -p "$W/assets" "$W/qhexrt" "$W/x" "$W/nested" # (1) EVERY release asset — no -p filter, get the whole set. gh release download "v$VERSION" --repo "$REPO" -D "$W/assets" # (2) The PRIVATE QHexRT artifact is NOT a release asset — pull it from the run. # Find the run that produced the release, then: gh run download <release-run-id> --repo "$REPO" --name sdk-electron-qhexrt-private --dir "$W/qhexrt" ``` ### (3) Checksums — `cd` into the directory first Sidecars contain a **bare basename**, so `shasum -c` fails with a misleading "FAILED open or read" if run from anywhere else. This will make every artifact look broken when nothing is wrong: ```bash cd "$W/assets" ok=0; bad=0 for s in *.sha256; do if shasum -a 256 -c "$s" >/dev/null 2>&1; then ok=$((ok+1)); else echo "MISMATCH: $s"; bad=$((bad+1)); fi done echo "verified=$ok mismatched=$bad" # bad MUST be 0 (cd "$W/qhexrt" && shasum -a 256 -c ./*.sha256) ``` ### (4) Extract everything — INCLUDING nested archives **The gotcha that matters:** `runanywhere-kotlin-maven-v*.zip` contains `.aar` files, and the `.aar`s are what hold the Android `.so` binaries. A one-level extraction reports "0 binaries" for the Maven bundle and every subsequent scan passes **vacuously**. **Recurse until the tree is dry — TWO levels are required here, not one.** Each `.aar` also contains a `classes.jar`, holding ~2027 `.class` files that are doubly unreachable: inside an unopened jar *and* excluded by the `.class` suffix rule most magic-byte scanners use. Level 2 adds **2033 files** (0 placeholder hits on v0.20.25); level 3 is empty. Loop until a pass finds no new archives rather than hardcoding a depth. ```bash cd "$W/assets" for f in *; do case "$f" in *.sha256|MANIFEST.txt) continue;; esac d="$W/x/$f"; mkdir -p "$d" case "$f" in *.zip) unzip -qq -o "$f" -d "$d" || echo "EXTRACT FAILED: $f" ;; *.tar.gz|*.tgz) tar -xzf "$f" -C "$d" || echo "EXTRACT FAILED: $f" ;; *) echo "UNHANDLED TYPE: $f" ;; esac done d="$W/x/$(basename "$W"/qhexrt/*.tgz)"; mkdir -p "$d"; tar -xzf "$W"/qhexrt/*.tgz -C "$d" # nested pass cd "$W/x" find . -type f \( -name '*.aar' -o -name '*.jar' -o -name '*.zip' -o -name '*.tgz' -o -name '*.tar.gz' \) \ | sed 's|^\./||' | while IFS= read -r a; do dd="$W/nested/$(printf '%s' "$a" | tr '/' '_')"; mkdir -p "$dd" case "$a" in *.aar|*.jar|*.zip) unzip -qq -o "$a" -d "$dd";; *) tar -xzf "$a" -C "$dd";; esac done ``` **Prove extraction actually happened** — an empty dir makes "0 hits" meaningless: ```bash for d in "$W"/x/*/; do n=$(find "$d" -type f | wc -l) [ "$n" -lt 2 ] && echo "SUSPICIOUSLY EMPTY: $d ($n files)" done ``` ### (5) Placeholder scan — EVERY file, not just binaries The staging URL can also live in JS/JSON/TS config, so scan by content across all file types, and additionally scan the raw compressed streams in case a member never got extracted: ```bash find "$W/x" "$W/nested" -type f | wc -l # total files covered grep -rla "YOUR_STAGING_BASE_URL" "$W/x" "$W/nested" | tee /tmp/ph.txt | wc -l # MUST be 0 for f in "$W"/assets/*.tgz "$W"/assets/*.tar.gz "$W"/qhexrt/*.tgz; do gzip -dc "$f" 2>/dev/null | grep -qa "YOUR_STAGING_BASE_URL" && echo "RAW HIT: $f" done ``` Find binaries by **magic bytes, not extension** (an unsuffixed framework binary like `RACommons.framework/RACommons` has no extension and a name-based `find` misses it). Signatures to cover: | Magic | Kind | v0.20.25 count | |---|---|---| | `7f454c46` | ELF | 177 | | `!<arch>` (`213c617263683e0a`) | static `.a` **and `.lib`** | 41 | | `MZ` | PE | 29 | | `cffaedfe` / `cefaedfe` | Mach-O | 19 | | `00 61 73 6d` | wasm | 10 | | **`4d544c42` (`MTLB`)** | **Metal library** — easy to miss | 5 | | `cafebabe` / `bebafeca` | fat Mach-O (exclude `.jar`/`.class`) | 1 | **Total: 282 binaries.** An earlier pass reported 277 because `MTLB` was absent from the signature list — `.metallib` files were invisible to the binary scan (they were still covered by the all-file `grep` in step 5, so the conclusion held, but the count was wrong). If your total comes in materially under ~282 for a comparable release, your detection is leaking. ### (6) POSITIVE staging-URL check — absence of the placeholder is NOT sufficient A build with an **empty** URL baked in passes a placeholder-only check. `check_plugin_natives.py` only tests absence, so add the positive assertion yourself. Two traps that produced false confidence on the v0.20.25 pass, both worth avoiding: - **Include `.lib`.** A `MUST_HAVE` regex of `rac_commons\.(dylib|so|dll|a)$` silently skips `RACommons-windows-x64/x64/rac_commons.lib` — an **89 MB** binary that then gets no positive check at all. Match `(dylib|so|dll|a|lib)` plus `\.wasm$`. Correct coverage is **38** binaries, not 37. - **Assert per binary, not by set-intersection.** Intersecting "non-public origins across all files" yields more than one origin here (`hf.co` *and* the staging host), so a binary carrying only `hf.co` would pass **vacuously**. Check each binary individually for the staging host. ```bash # v0.20.25 baseline: 38 commons/wasm/lib binaries; 0 missing the staging host. ``` Never print the URL — it is a repo secret. Report a truncated SHA-256 fingerprint, or just assert presence/absence counts. ### (7) Routability gate over the whole tree ```bash python3 scripts/validation/gates/check_plugin_natives.py "$W/x" "$W/nested" > /tmp/gate.txt 2>&1 grep -cE '^ ok ' /tmp/gate.txt; grep -E '^ FAIL' /tmp/gate.txt ``` v0.20.25 baseline: **77 ok, 1 FAIL**. The single expected FAIL is `RACommons-linux-x86_64/lib/librunanywhere_sherpa.so` — see the known-exception note below. **Any other FAIL blocks the publish.** ### (8) Known exception — Linux sherpa is a non-routable shell (PRE-EXISTING, not a regression) `RACommons-linux-x86_64-v*.tar.gz` ships **no** `libsherpa-onnx-c-api.so` at all and its `librac_backend_sherpa.so` is a ~156 KB shell (~3 sherpa strings, vs ~31 MB / ~192 for a healthy one on Electron macOS) — the engine is not linked in, so the carrier cannot reference `g_sherpa_{stt,tts,vad}_ops` and **STT/TTS/VAD cannot work from that bundle**. Android's carriers pass on all 3 ABIs, so this is Linux-only. **Compare the CARRIER, not the backend.** The gate FAILs on `librunanywhere_sherpa.so` (the carrier); citing `librac_backend_sherpa.so` as your evidence is comparing a *different file* and is not proof. The carrier is byte-identical across releases, which is the real proof: ```bash gh release download "v<PREV>" --repo "$REPO" -p 'RACommons-linux-x86_64-v<PREV>.tar.gz' -D /tmp/prev tar -xzf /tmp/prev/*.tar.gz -C /tmp/prev shasum -a 256 /tmp/prev/lib/librunanywhere_sherpa.so "$W"/x/RACommons-linux-*/lib/librunanywhere_sherpa.so # v0.20.24 and v0.20.25 both: size=15712 sha256=28ef16015b61da86... # strongest check — run the gate on the PREVIOUS bundle and confirm the identical FAIL: python3 scripts/validation/gates/check_plugin_natives.py /tmp/prev ``` If it ever regresses *further* (e.g. Android or macOS sherpa also goes hollow), stop and treat it as a blocking defect. ### (9) Account for every payload explicitly Print a per-artifact table (checksum verdict, binary count, placeholder hits) and confirm the row count equals the payload count. Artifacts that legitimately contain **no** native binaries, and why — do not let these pass silently unexamined: | Artifact | Why it has no binaries | |---|---| | `RunAnywhereMLXResources-ios-*.zip` | 25 third-party license/notice text files | | `runanywhere-kotlin-maven-v*.zip` | binaries live in the **nested** `.aar`s (step 4) | | `runanywhere-proto-ts-*.tgz` | pure TS/JS (69 `.ts` + 69 `.js`) | Everything else must have a non-zero, plausible binary count. A platform suddenly shipping fewer binaries than the previous release is a regression signal worth chasing before publishing. ### (10) Blind spot to be aware of — Windows x64 has NO routability coverage `RACommons-windows-x64-v*.zip` ships **only** a static `rac_commons.lib` (~89 MB) plus 152 `.h` / 11 `.hpp` headers — **zero backend DLLs**, where Linux/Android/Web each ship 4–5 backend carriers. That is consistent with a static-embedding bundle (consumers link the engines in themselves), but the consequence matters: **no gate can detect a missing or hollow Windows backend**, because there is no carrier to inspect. `check_plugin_natives.py` therefore reports nothing at all for this artifact rather than reporting health. Windows x64 is also *advisory* in `release.yml` (its absence does not fail the release). So a Windows regression can pass every automated check. If Windows correctness matters for a given release, verify it by actually running something on the real Windows box (see §Electron for how to reach it) — do not infer it from a green pipeline. Worth a deliberate confirmation with the repo owner that the DLL-free shape is intentional. --- ## General verification discipline (every artifact, every registry)
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기