diff --git a/scripts/cascade-publish.sh b/scripts/cascade-publish.sh index 14892026e..706ec5b07 100755 --- a/scripts/cascade-publish.sh +++ b/scripts/cascade-publish.sh @@ -10,7 +10,17 @@ # - Workspace version already bumped (Cargo.toml + per-crate Cargo.toml refs) # - Cargo.lock regenerated (cargo check --workspace) # - All workspace tests pass -# - CARGO_REGISTRY_TOKEN exported or `cargo login` already run +# - A VALID crates.io token in ~/.cargo/credentials.toml (`cargo login`). +# +# AUTH GOTCHA (v0.60.0, ~2h lost): if $CARGO_REGISTRY_TOKEN is exported with a +# STALE value, cargo uses it over the valid credentials file and every upload +# fails "403 authentication failed" (dry-runs pass — they skip upload). Run this +# with `unset CARGO_REGISTRY_TOKEN` unless you KNOW the env token is fresh. +# +# CONFIG GOTCHA (v0.60.0): publish from a tree with NO .cargo/config.toml. The +# dev-only [patch.crates-io] there points siblings at ../ paths that don't +# exist in a git worktree → "failed to load source". Remove it first; the +# consolidated monorepo resolves siblings via in-tree path deps. # # This script bypasses `make publish` (which has a known issue with .cargo/config.toml # stub interaction on some crates per the v0.34.0 cascade observation) and uses @@ -20,7 +30,7 @@ set +e # don't exit on individual crate failures — track each and report at e # Tier definitions per SPEC-HF-PUBLISH-001 § crates.io release cascade. declare -A TIERS -TIERS[1]="aprender-contracts-macros aprender-quant aprender-gemm-codegen aprender-sparse aprender-solve aprender-rand aprender-fft aprender-image aprender-tensor aprender-cupti" +TIERS[1]="apr-format aprender-contracts-macros aprender-quant aprender-gemm-codegen aprender-sparse aprender-solve aprender-rand aprender-fft aprender-image aprender-tensor aprender-cupti" TIERS[2]="aprender-contracts aprender-core aprender-profile-core aprender-graph" TIERS[3]="aprender-profile" TIERS[4]="aprender-gpu" @@ -32,7 +42,7 @@ TIERS[9]="aprender-present-core aprender-present-layout aprender-present-yaml ap TIERS[10]="apr-cli" TIERS[11]="aprender" TIERS[12]="aprender-tsp aprender-monte-carlo aprender-shell" -TIERS[13]="aprender-test-derive aprender-test-lib aprender-test-js-gen aprender-test-cli aprender-test-showcase aprender-zram-core aprender-zram-adaptive aprender-zram-generator aprender-zram-cli aprender-zram aprender-present-test-macros aprender-present-test aprender-present-lib aprender-present-cli aprender-db aprender-rag aprender-viz aprender-registry aprender-distribute aprender-simulate aprender-verify aprender-verify-ml aprender-train-shell aprender-train-inspect aprender-train-bench aprender-train-wasm aprender-contracts-cli" +TIERS[13]="aprender-test-derive aprender-test-lib aprender-test-js-gen aprender-test-cli aprender-test-showcase aprender-zram-core aprender-zram-adaptive aprender-zram-generator aprender-zram-cli aprender-zram aprender-present-test-macros aprender-present-test aprender-present-lib aprender-present-cli aprender-db aprender-rag aprender-viz aprender-registry aprender-distribute aprender-simulate aprender-verify aprender-verify-ml aprender-train-shell aprender-train-inspect aprender-train-bench aprender-train-wasm aprender-contracts-cli aprender-rag-cli" TARGET_VERSION=$(grep -E '^version = "' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/') [ -z "$TARGET_VERSION" ] && { echo "ERROR: could not detect target version from Cargo.toml"; exit 1; } @@ -43,7 +53,12 @@ ONLY_TIER="${2:-}" check_version() { local crate=$1 - curl -s "https://crates.io/api/v1/crates/$crate" 2>/dev/null \ + # crates.io REJECTS requests without a User-Agent (returns an error page, not + # JSON) — omitting it made this always print "none", which silently broke the + # --check STATUS report AND the FINAL VERIFICATION gate (every crate looked + # unpublished). Always send a UA. (v0.60.0 cascade lesson.) + curl -s -H "User-Agent: aprender-cascade-publish (release automation)" \ + "https://crates.io/api/v1/crates/$crate" 2>/dev/null \ | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('crate',{}).get('max_version','none'))" 2>/dev/null } @@ -56,7 +71,7 @@ publish_crate() { fi echo -n " → $crate ($cur → $TARGET_VERSION): " local out - out=$(cargo publish -p "$crate" --allow-dirty --locked 2>&1 | tail -3) + out=$(cargo publish -p "$crate" --allow-dirty --locked 2>&1 | tail -6) if echo "$out" | grep -q "Published $crate"; then echo "✓ PUBLISHED" sleep 10 # let crates.io index settle before dependents try to fetch @@ -64,10 +79,25 @@ publish_crate() { elif echo "$out" | grep -qE "already.*upload|already exists"; then echo "(already on registry)" return 0 + # Surface the two FATAL classes that are NOT dep-ordering deferrals — a bare + # "Caused by:" truncation hid both for ~2h in the v0.60.0 cascade: + # 1. 403 authentication failed — a STALE $CARGO_REGISTRY_TOKEN env var + # overrides a valid ~/.cargo/credentials.toml. Fix: `unset + # CARGO_REGISTRY_TOKEN` so the file token is used (or refresh the env one). + # 2. failed to load source for dependency `` — a dev-only + # [patch.crates-io] in .cargo/config.toml points at ../ paths that + # don't exist in a worktree. Fix: remove .cargo/config.toml before publish + # (the consolidated monorepo resolves siblings via in-tree path deps). + elif echo "$out" | grep -qiE "403|authentication failed"; then + echo "FATAL-AUTH (403 — unset stale \$CARGO_REGISTRY_TOKEN; use ~/.cargo/credentials.toml)" + return 1 + elif echo "$out" | grep -qiE "failed to load source|no such file or directory"; then + echo "FATAL-CONFIG (dev [patch.crates-io] in .cargo/config.toml — remove it before publish)" + return 1 else local err err=$(echo "$out" | grep -oE "candidate versions found which didn't match:.*$" | head -1) - [ -z "$err" ] && err=$(echo "$out" | grep -oE "Caused by:.*$" | head -1) + [ -z "$err" ] && err=$(echo "$out" | grep -oiE "(error|caused by)[:].*$" | head -1) [ -z "$err" ] && err=$(echo "$out" | head -1) echo "DEFER ($err)" return 1