-
Notifications
You must be signed in to change notification settings - Fork 86
install: suggest glm-5.3-flash and run under POSIX sh #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
375668c
fdd8864
a705c28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| #!/usr/bin/env python3 | ||
| """Fail if a retired hosted model id appears anywhere a person or an agent is | ||
| told what to type. | ||
|
|
||
| A retired id is refused by the gateway with 403 model_not_entitled, so an | ||
| installer hint or a skill that names one sends every new user straight into an | ||
| error. install.sh printed `wally opencode --cloud -m glm-5.3` in its "Next:" | ||
| block, and the RunAnywhere skill the installer copies into agents' skill | ||
| folders used the same id, long after `glm-5.3-flash` replaced it. | ||
|
|
||
| RETIRED mirrors `launch_models.retired` in InferenceInfra's | ||
| contracts/public/status_semantics.json, which is the machine-readable list of | ||
| what RunAnywhere serves. When the service retires an id, add it here. | ||
|
|
||
| python3 scripts/ci/check-retired-model-ids.py | ||
|
|
||
| Scans user-facing text only: the installers, READMEs, docs, skills and the CLI | ||
| source (help text). tests/ is excluded on purpose -- fixtures there use | ||
| arbitrary ids, including retired ones a real usage history still carries. | ||
| Exits non-zero and prints file:line for every hit. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import re | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| ROOT = Path(__file__).resolve().parent.parent.parent | ||
|
|
||
| RETIRED = ("glm-5.3", "glm-5.2", "gemini-2.5-flash") | ||
|
|
||
| # Files and trees a user or an agent reads instructions from. | ||
| SCANNED = ( | ||
| "install.sh", | ||
| "install.ps1", | ||
| "README.md", | ||
| "CONTRIBUTING.md", | ||
| "AGENTS.md", | ||
| "docs", | ||
| "skills", | ||
| ".claude/skills", | ||
| ".agents/skills", | ||
| "src", | ||
| ) | ||
| TEXT_SUFFIXES = {".sh", ".ps1", ".md", ".cpp", ".h", ".hpp", ".swift", ".txt", ""} | ||
|
|
||
|
|
||
| def pattern_for(model_id: str) -> re.Pattern[str]: | ||
| """Match `model_id` as a whole id: `glm-5.3` but not `glm-5.3-flash`, | ||
| `glm-5.30` or `xglm-5.3`. A sentence-ending period still counts as a hit.""" | ||
| return re.compile( | ||
| r"(?<![A-Za-z0-9_.-])" + re.escape(model_id) + r"(?![A-Za-z0-9_-]|\.[A-Za-z0-9])" | ||
| ) | ||
|
|
||
|
|
||
| PATTERNS = tuple((model_id, pattern_for(model_id)) for model_id in RETIRED) | ||
|
|
||
|
|
||
| def find_retired(text: str) -> list[tuple[int, str]]: | ||
| """(line number, retired id) for every retired id in `text`.""" | ||
| hits = [] | ||
| for number, line in enumerate(text.splitlines(), start=1): | ||
| for model_id, pattern in PATTERNS: | ||
| if pattern.search(line): | ||
| hits.append((number, model_id)) | ||
| return hits | ||
|
|
||
|
|
||
| def scanned_files(root: Path = ROOT) -> list[Path]: | ||
| files = [] | ||
| for entry in SCANNED: | ||
| path = root / entry | ||
| if path.is_file(): | ||
| files.append(path) | ||
| elif path.is_dir(): | ||
| files.extend( | ||
| candidate | ||
| for candidate in sorted(path.rglob("*")) | ||
| if candidate.is_file() and candidate.suffix in TEXT_SUFFIXES | ||
| ) | ||
| return files | ||
|
|
||
|
|
||
| def scan(root: Path = ROOT) -> list[str]: | ||
| findings = [] | ||
| for path in scanned_files(root): | ||
| try: | ||
| text = path.read_text(encoding="utf-8") | ||
| except UnicodeDecodeError: | ||
| continue | ||
| for number, model_id in find_retired(text): | ||
| findings.append(f"{path.relative_to(root)}:{number}: retired model id '{model_id}'") | ||
| return findings | ||
|
|
||
|
|
||
| def main() -> int: | ||
| findings = scan() | ||
| if findings: | ||
| print("\n".join(findings), file=sys.stderr) | ||
| print( | ||
| f"{len(findings)} retired model id(s) in user-facing text; the gateway " | ||
| "refuses these with 403 model_not_entitled.", | ||
| file=sys.stderr, | ||
| ) | ||
| return 1 | ||
| print(f"no retired model ids in {len(scanned_files())} user-facing files") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| #!/usr/bin/env bash | ||
| # Proves install.sh behaves identically under bash, dash and plain `sh` -- | ||
| # the shell `curl ... | sh` actually resolves to on Debian/Ubuntu (dash), | ||
| # on macOS (bash) and wherever `sh` is something else POSIX. Runs the real | ||
| # install.sh under all three with a stubbed curl/uname, a fixture release, | ||
| # tarball and checksum, and no network, then diffs the output byte for byte. | ||
| # | ||
| # Covers the cases PR #79 claims are shell-independent: a full happy-path | ||
| # install, an unsupported platform, a bad checksum, and a failed release | ||
| # lookup (the case that motivated the POSIX rewrite -- with `pipefail`, | ||
| # dash died on `set -o pipefail` before printing anything; without it, the | ||
| # failure must still reach `fail` and print a message on all three shells). | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| INSTALL="${SCRIPT_DIR}/../../install.sh" | ||
| WORK="$(mktemp -d)" | ||
| trap 'rm -rf "$WORK"' EXIT | ||
|
|
||
| STUB="$WORK/stub-bin" | ||
| mkdir -p "$STUB" | ||
|
|
||
| # Fake curl: serves a canned GitHub release response, tarball or checksum | ||
| # from $WALLY_STUB_DIR by matching the requested URL, the same way the real | ||
| # calls in install.sh shape theirs. A fixture that does not exist fails the | ||
| # way a real network error would (curl's own exit code for an HTTP failure). | ||
| cat > "$STUB/curl" <<'CURL' | ||
| #!/bin/sh | ||
| out="" | ||
| url="" | ||
| while [ $# -gt 0 ]; do | ||
| case "$1" in | ||
| -o) shift; out="$1" ;; | ||
| http*) url="$1" ;; | ||
| esac | ||
| shift | ||
| done | ||
| case "$url" in | ||
| *api.github.com/repos/*/releases/latest) body="$WALLY_STUB_DIR/release.json" ;; | ||
| *.sha256) body="$WALLY_STUB_DIR/asset.sha256" ;; | ||
| *) body="$WALLY_STUB_DIR/asset.tar.gz" ;; | ||
| esac | ||
| [ -f "$body" ] || exit 22 | ||
| if [ -n "$out" ]; then cp "$body" "$out"; else cat "$body"; fi | ||
| CURL | ||
| chmod +x "$STUB/curl" | ||
|
|
||
| # Fake uname: reports whatever OS/arch the case under test wants. | ||
| cat > "$STUB/uname" <<'UNAME' | ||
| #!/bin/sh | ||
| case "$1" in | ||
| -s) echo "${WALLY_STUB_OS:-Darwin}" ;; | ||
| -m) echo "${WALLY_STUB_ARCH:-arm64}" ;; | ||
| esac | ||
| UNAME | ||
| chmod +x "$STUB/uname" | ||
|
|
||
| # A good fixture: a release tarball whose bin/wally answers --version, | ||
| # whoami and login the way the real binary does, plus a matching sha256. | ||
| GOOD="$WORK/fixture-good" | ||
| mkdir -p "$GOOD/wally-macos-arm64/bin" | ||
| cat > "$GOOD/wally-macos-arm64/bin/wally" <<'WALLY' | ||
| #!/bin/sh | ||
| case "$1" in | ||
| --version) echo "wally 1.2.3 (stub)" ;; | ||
| whoami) exit 1 ;; | ||
| login) echo "stub login ok" ;; | ||
| esac | ||
| WALLY | ||
| chmod +x "$GOOD/wally-macos-arm64/bin/wally" | ||
| ( cd "$GOOD" && tar -czf asset.tar.gz wally-macos-arm64 ) | ||
| echo '{"tag_name": "v1.2.3"}' > "$GOOD/release.json" | ||
| shasum -a 256 "$GOOD/asset.tar.gz" | awk '{print $1" wally-1.2.3-macos-arm64.tar.gz"}' > "$GOOD/asset.sha256" | ||
|
|
||
| # A fixture whose checksum does not match its tarball. | ||
| BADSUM="$WORK/fixture-badsum" | ||
| mkdir -p "$BADSUM" | ||
| cp "$GOOD/release.json" "$GOOD/asset.tar.gz" "$BADSUM/" | ||
| printf '%s wally-1.2.3-macos-arm64.tar.gz\n' \ | ||
| "0000000000000000000000000000000000000000000000000000000000000000" \ | ||
| > "$BADSUM/asset.sha256" | ||
|
|
||
| # A fixture with no files at all, so the release lookup fails as if the | ||
| # network were down. | ||
| EMPTY="$WORK/fixture-empty" | ||
| mkdir -p "$EMPTY" | ||
|
|
||
| fails=0 | ||
| check() { | ||
| name="$1"; expected="$2"; actual="$3" | ||
| if [ "$expected" = "$actual" ]; then | ||
| printf 'ok %s\n' "$name" | ||
| else | ||
| printf 'FAIL %s\n --- expected ---\n%s\n --- actual ---\n%s\n' \ | ||
| "$name" "$expected" "$actual" | ||
| fails=$((fails + 1)) | ||
| fi | ||
| } | ||
|
|
||
| # Runs install.sh under one shell for one case and prints | ||
| # "<exit-code>\n<stdout+stderr>", with that run's own $HOME path normalized | ||
| # out -- a happy-path install embeds $HOME in its output (install dir, skill | ||
| # path, binary path), and each shell gets its own HOME so the three runs | ||
| # cannot clobber each other's install tree. | ||
| run_case() { | ||
| shell="$1"; stub_dir="$2"; os="$3"; arch="$4" | ||
| home="$WORK/home-$shell" | ||
| rm -rf "$home"; mkdir -p "$home" | ||
| set +e | ||
| out="$(WALLY_STUB_DIR="$stub_dir" WALLY_STUB_OS="$os" WALLY_STUB_ARCH="$arch" \ | ||
| HOME="$home" PATH="$STUB:$PATH" "$shell" "$INSTALL" 2>&1)" | ||
| code=$? | ||
| set -e | ||
| printf '%s\n%s' "$code" "$(printf '%s' "$out" | sed "s#$home#\$HOME#g")" | ||
| } | ||
|
|
||
| for case_name in happy-path unsupported-platform bad-checksum failed-release-lookup; do | ||
| case "$case_name" in | ||
| happy-path) stub="$GOOD"; os="Darwin"; arch="arm64" ;; | ||
| unsupported-platform) stub="$GOOD"; os="Darwin"; arch="x86_64" ;; | ||
| bad-checksum) stub="$BADSUM"; os="Darwin"; arch="arm64" ;; | ||
| failed-release-lookup) stub="$EMPTY"; os="Darwin"; arch="arm64" ;; | ||
| esac | ||
| bash_out="$(run_case bash "$stub" "$os" "$arch")" | ||
| dash_out="$(run_case dash "$stub" "$os" "$arch")" | ||
| sh_out="$(run_case sh "$stub" "$os" "$arch")" | ||
| check "$case_name: dash byte-identical to bash" "$bash_out" "$dash_out" | ||
| check "$case_name: sh byte-identical to bash" "$bash_out" "$sh_out" | ||
|
Comment on lines
+127
to
+128
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Assert each fixture’s expected result. The 🤖 Prompt for AI Agents |
||
| done | ||
|
|
||
| [ "$fails" -eq 0 ] || { printf '%d comparison(s) failed\n' "$fails" >&2; exit 1; } | ||
| printf 'all cross-shell cases byte-identical\n' | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: RunanywhereAI/wally
Length of output: 157
🏁 Script executed:
Repository: RunanywhereAI/wally
Length of output: 1921
Run
bash -nonce for each script.Bash parses only the first script path.
scripts/release/update-tap.shbecomes an argument toscripts/build/package-wally.sh, so syntax errors in the second script can bypass this check.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents