app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
← Commit history
Release tiers (pup-bridge#86): ship.sh -> insiders by default; promote.sh/rollback.sh move the public tier only on a human's attested words
6 files changed
+216−63
.gitignore+1@@ -18,3 +18,4 @@ adom-bridge-puppeteer-source.tar.gz !assets/pup-icons/*.png !dev-skills/pup-hero-image/assets/**/*.png !dev-skills/pup-hero-image/assets/*.png+dist/
CLAUDE.md+15−7@@ -44,14 +44,22 @@ via the `updateManifestUrl` in `bridge.json`. (Extracted from `adom-bridge/plugi - `package.json` (root, skillpack) — declares `dependencies:{ "adom/adom-bridge": "^1.9.x" }` so `pkg install` pulls the AD CLI + core skills. SERVER npm deps live ONLY in the Release zip's `src/package.json`. -## The publish/reinstall loop+## The publish/reinstall loop — TWO TIERS (adopted 2026-09-02, pup-bridge#86) -See `PUBLISHING.md` for the full recipe. Short form: edit `src/` → bump `src/BRIDGE_VERSION` +-`src/bridge.json` together → build `adom-bridge-puppeteer-v<ver>.zip` from `src/` (zip-root, no top dir)-→ `adom-wiki release upload` the zip → point `adom-bridge-puppeteer-manifest.json` at that release URL-(with the zip's real sha256/size) → `adom-wiki repo push` the manifest + changed source + SKILL.md →-`adom-bridge-cli bridge_install {"manifestUrl": ".../files/adom-bridge-puppeteer-manifest.json","force":true}`-to test → verify in pup. Wiki = canonical.+`tools/ship.sh <ver> "<changelog>"` ships to the **INSIDERS** tier: the private page+`adom/pup-bridge-insiders`, whose READERS are the tier (John's desktops today). Desktops on ab ≥2.0.99+probe `insidersManifestUrl` (declared in `src/bridge.json`) first with the user's own wiki login and+follow it when they can read it; everyone else silently follows the PUBLIC manifest on+`adom/pup-bridge`, which ship.sh never touches. The public tier moves ONLY through+`tools/promote.sh <ver> "<the human's exact words>" --by "<name>"`, which refuses without an+attestation, uploads the SAME archived bytes (`dist/<ver>/`, sha-checked against the insiders record),+records the words in the public manifest (`promotion_attestation`), pushes the source, and publishes+the skills pkg (pkg tiering waits on adom/wiki#168, so pkg = public tier). `tools/rollback.sh` re-points+the public manifest at an earlier promoted build on the same rule. One plain semver per build in both+tiers; never `-beta` suffixes. An AI passes the human's words through verbatim or does not promote.+Test an insiders build on a desktop with `adom-bridge --target <box> bridge_install {"manifestUrl":+".../pup-bridge-insiders/files/adom-bridge-puppeteer-insiders-manifest.json","force":true}` (needs+that desktop's login to read the page). Wiki = canonical. Full recipe: `PUBLISHING.md`. **ALWAYS reload/recreate every pup window after a deploy (standing rule from John, 2026-08-11):** after EVERY `bridge_install`, close and reopen every window this thread owns (`pup_my_windows` →
PUBLISHING.md+12@@ -84,3 +84,15 @@ adom-bridge-cli bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pag - node_modules is NEVER in the zip (size/limits/native builds) — AD reconstructs it on spawn. - Keep `src/BRIDGE_VERSION` and `src/bridge.json` version in lockstep, and > the bundled seed so cache-over-bundled wins.++## Release tiers (2026-09-02, pup-bridge#86)++- `tools/ship.sh <ver> "<log>"` → INSIDERS (private page `adom/pup-bridge-insiders`; readers = tier).+ Archives `dist/<ver>/{zip, insiders-manifest.json}`. Public tier untouched. No pkg publish.+- `tools/promote.sh <ver> "<human's exact words>" --by "<name>"` → PUBLIC. Refuses without the words;+ same bytes (sha-checked); records `promotion_attestation`/`promoted_by`/`promoted_at` in the public+ manifest; pushes source; publishes the skills pkg; verifies the top row carries the tarball.+- `tools/rollback.sh <ver> "<words>" --by "<name>"` → re-points the public manifest at a previously+ promoted build (`dist/<ver>/public-manifest.json`), recording `rollback_attestation`.+- `src/bridge.json` carries BOTH `updateManifestUrl` (public) and `insidersManifestUrl` (private), in+ both tiers' zips. ab <2.0.99 ignores the insiders field.
tools/promote.shadded+93@@ -0,0 +1,93 @@+#!/usr/bin/env bash+# promote.sh — move an INSIDERS build to the PUBLIC tier, on a human's exact words.+#+# tools/promote.sh <version> "<the human's exact attestation words>" [--by "<name>"]+#+# pup-bridge#86 / ab 2.0.99 release tiers: the public page (adom/pup-bridge) moves ONLY through+# this script, and this script REFUSES without an attestation. An AI passes the human's words+# through verbatim or does not promote — never on its own judgment, never because a sweep was+# green. The words travel IN the public manifest (promotion_attestation / promoted_by /+# promoted_at) so the record of who approved the rollout rides with the release.+#+# Same signed bytes in both tiers: the zip archived by tools/ship.sh under dist/<ver>/ is+# uploaded as-is and its sha256 is checked against the insiders manifest before anything moves.+# The skills pkg is published HERE (not at insiders-ship time): registry dist-tag tiering is+# adom/wiki#168, so `pkg latest` is effectively the public tier.+set -euo pipefail+VER="${1:?usage: tools/promote.sh <version> \"<attestation words>\" [--by name]}"+ATTEST="${2:-}"+shift 2 || true+BY="${ADOM_PROMOTED_BY:-}"+while [ $# -gt 0 ]; do case "$1" in --by) BY="$2"; shift 2;; *) shift;; esac; done+REPO="$(cd "$(dirname "$0")/.." && pwd)"; cd "$REPO"+PUBLIC="adom/pup-bridge"+INSIDERS="adom/pup-bridge-insiders"+ZIP="adom-bridge-puppeteer-v${VER}.zip"+ARCH="dist/${VER}"++echo "== 0. attestation gate =="+A="$(printf '%s' "$ATTEST" | tr -s '[:space:]' ' ' | sed 's/^ //;s/ $//')"+if [ "${#A}" -lt 8 ] || printf '%s' "$A" | grep -qiE '^(ok|yes|promote|ship it|go|approved|test|placeholder|x+)$'; then+ echo "REFUSED: promoting to the PUBLIC tier needs the human's exact words (>= 8 chars, not a placeholder)."+ echo " e.g. tools/promote.sh $VER \"John: 2.0.380 is good on all three of my boxes, ship it to everyone\" --by \"John Lauer\""+ exit 2+fi+[ -n "$BY" ] || { echo "REFUSED: say who promoted (--by \"<name>\" or ADOM_PROMOTED_BY)."; exit 2; }+echo " attested by: $BY"+echo " words: \"$A\""++echo "== 1. the archived insiders build =="+[ -f "$ARCH/$ZIP" ] || { echo "MISSING $ARCH/$ZIP — ship $VER to insiders first (tools/ship.sh $VER ...)"; exit 1; }+[ -f "$ARCH/insiders-manifest.json" ] || { echo "MISSING $ARCH/insiders-manifest.json — the insiders record for $VER"; exit 1; }+SHA_LOCAL=$(python3 -c "import hashlib,sys;print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$ARCH/$ZIP")+SHA_INS=$(python3 -c "import json,sys;print(json.load(open(sys.argv[1]))['sha256'])" "$ARCH/insiders-manifest.json")+[ "$SHA_LOCAL" = "$SHA_INS" ] || { echo "SHA MISMATCH: archived zip $SHA_LOCAL vs insiders manifest $SHA_INS — refusing to promote different bytes"; exit 1; }+echo " $ZIP sha256=$SHA_LOCAL matches the insiders manifest (same signed bytes)"+ON_DISK=$(cat src/BRIDGE_VERSION)+[ "$ON_DISK" = "$VER" ] || { echo "TREE MISMATCH: working tree is at $ON_DISK, promoting $VER — the public source push must match the promoted runtime. Check out / re-ship $VER first."; exit 1; }++echo "== 2. public release =="+adom-wiki release create "$PUBLIC" "$VER" >/dev/null 2>&1 || true+UP=$(adom-wiki release upload "$PUBLIC" "$VER" "$ARCH/$ZIP" --json 2>&1 | tail -c 4000)+python3 - "$UP" "$VER" "$ZIP" "$ARCH" "$A" "$BY" <<'PY'+import json, sys, datetime, hashlib, os+raw, ver, zipname, arch, words, by = sys.argv[1:7]+i = raw.find('{'); d = json.loads(raw[i:]) if i >= 0 else {}+a = d.get('data') or d.get('asset') or d+url = a.get('download_url') or f"https://wiki.adom.inc/download/adom/pup-bridge/{ver}/{zipname}"+if not url.startswith('http'): url = 'https://wiki.adom.inc' + url+sha = hashlib.sha256(open(os.path.join(arch, zipname), 'rb').read()).hexdigest(); size = os.path.getsize(os.path.join(arch, zipname))+ins = json.load(open(os.path.join(arch, 'insiders-manifest.json')))+now = datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')+m = json.load(open('adom-bridge-puppeteer-manifest.json'))+m.update({'version': ver, 'url': url, 'sha256': sha, 'size': size, 'released_at': now,+ 'insiders_released_at': ins.get('released_at'),+ 'promotion_attestation': words, 'promoted_by': by, 'promoted_at': now})+m.pop('rollback_attestation', None); m.pop('rolled_back_by', None); m.pop('rolled_back_at', None)+json.dump(m, open('adom-bridge-puppeteer-manifest.json', 'w'), indent=2); open('adom-bridge-puppeteer-manifest.json', 'a').write('\n')+json.dump(m, open(os.path.join(arch, 'public-manifest.json'), 'w'), indent=2)+print(f" public manifest -> {url}\n sha256={sha} size={size}\n archived {arch}/public-manifest.json")+PY++echo "== 3. push the public source + manifest =="+adom-wiki repo push "$PUBLIC" \+ --files adom-bridge-puppeteer-manifest.json src/bridge.json src/BRIDGE_VERSION src/*.js src/extension/*.js src/extension/*.json SKILL.md package.json \+ -m "v${VER}: promoted to public — ${BY}: ${A}" >/dev/null 2>&1 || {+ [ "${FORCE_PUSH:-0}" = "1" ] || { echo " PUSH FAILED (set FORCE_PUSH=1 to retry)"; exit 1; }+ }++echo "== 4. skills pkg (public tier = pkg latest until adom/wiki#168) =="+PUBOUT=$(adom-wiki pkg publish --org adom 2>&1); PUBRC=$?+echo "$PUBOUT" | tail -3+[ $PUBRC -eq 0 ] || { echo "PKG PUBLISH FAILED (exit $PUBRC) — top row would have no tarball (pup-bridge#66)"; echo "$PUBOUT" | tail -15; exit 1; }+adom-wiki pkg info adom/pup-bridge 2>/dev/null | python3 -c "+import sys,json+raw=sys.stdin.read()+d,_=json.JSONDecoder().raw_decode(raw[raw.find('{'):])+v=d['versions'][0]+assert v.get('integrity') and v.get('size_bytes'), f\"top row {v['version']} has NO tarball\"+print(f\" pkg tarball verified on top row {v['version']} ({v['size_bytes']} bytes)\")" || { echo "PKG TARBALL VERIFY FAILED"; exit 1; }++echo "== 5. DIFF-VERIFY =="+MAN=$(python3 -c "import json;print(json.load(open('adom-bridge-puppeteer-manifest.json'))['version'])")+[ "$MAN" = "$VER" ] && echo "PROMOTED $VER to PUBLIC (attested by $BY)" || { echo "PUBLIC MANIFEST NOT AT $VER (got $MAN)"; exit 1; }
tools/rollback.shadded+37@@ -0,0 +1,37 @@+#!/usr/bin/env bash+# rollback.sh — re-point the PUBLIC tier at an earlier promoted build, on a human's exact words.+#+# tools/rollback.sh <version> "<the human's exact words>" [--by "<name>"]+#+# Fast path for a bad public promote: the target must have been promoted before (its+# dist/<ver>/public-manifest.json exists, i.e. the public release asset is already on the page),+# so this only rewrites and pushes the public manifest — no upload, no source push, no pkg+# publish (the skills pkg is not rolled back here; do that deliberately if the skills changed).+set -euo pipefail+VER="${1:?usage: tools/rollback.sh <version> \"<words>\" [--by name]}"+ATTEST="${2:-}"; shift 2 || true+BY="${ADOM_PROMOTED_BY:-}"+while [ $# -gt 0 ]; do case "$1" in --by) BY="$2"; shift 2;; *) shift;; esac; done+REPO="$(cd "$(dirname "$0")/.." && pwd)"; cd "$REPO"+PUBLIC="adom/pup-bridge"+A="$(printf '%s' "$ATTEST" | tr -s '[:space:]' ' ' | sed 's/^ //;s/ $//')"+[ "${#A}" -ge 8 ] || { echo "REFUSED: rollback needs the human's exact words (>= 8 chars)."; exit 2; }+[ -n "$BY" ] || { echo "REFUSED: say who rolled back (--by \"<name>\")."; exit 2; }+SRC="dist/${VER}/public-manifest.json"+[ -f "$SRC" ] || { echo "MISSING $SRC — $VER was never promoted from this checkout; promote it (tools/promote.sh) instead of rolling back to it."; exit 1; }+python3 - "$SRC" "$A" "$BY" <<'PY'+import json, sys, datetime+src, words, by = sys.argv[1:4]+old = json.load(open(src))+now = datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')+m = json.load(open('adom-bridge-puppeteer-manifest.json'))+for k in ('version', 'url', 'sha256', 'size', 'released_at', 'insiders_released_at', 'promotion_attestation', 'promoted_by', 'promoted_at'):+ if k in old: m[k] = old[k]+m.update({'rollback_attestation': words, 'rolled_back_by': by, 'rolled_back_at': now})+json.dump(m, open('adom-bridge-puppeteer-manifest.json', 'w'), indent=2); open('adom-bridge-puppeteer-manifest.json', 'a').write('\n')+print(f" public manifest -> {old['version']} ({old['url']})")+PY+adom-wiki repo push "$PUBLIC" --files adom-bridge-puppeteer-manifest.json -m "ROLLBACK public tier to v${VER} — ${BY}: ${A}" >/dev/null 2>&1 || {+ [ "${FORCE_PUSH:-0}" = "1" ] || { echo " PUSH FAILED (set FORCE_PUSH=1 to retry)"; exit 1; }+}+echo "ROLLED BACK public tier to $VER (attested by $BY). Desktops pick it up on their next update check."
tools/ship.sh+58−56@@ -4,34 +4,49 @@ # It used to live in /tmp and evaporated with every /tmp clear (twice now, mid-session), so the # recipe in PUBLISHING.md had to be hand-run from memory. It belongs here. #-# tools/ship.sh <version> "<changelog sentence>" [PUBLISH_PKG=0|1]+# tools/ship.sh <version> "<changelog sentence>" # -> INSIDERS tier (the default)+# tools/promote.sh <version> "<human's words>" --by "<name>" # -> PUBLIC tier, attested+# tools/rollback.sh <version> "<human's words>" --by "<name>" # re-point PUBLIC at an older build #-# Steps: verify the tree parses -> bump BRIDGE_VERSION + bridge.json (lockstep) -> zip src/ at the-# zip ROOT (no node_modules) -> release create/upload -> point the manifest at the release asset with-# its REAL sha256/size -> repo push the text files -> DIFF-VERIFY the version actually landed.+# RELEASE TIERS (pup-bridge#86, ab 2.0.99 insidersManifestUrl, adopted 2026-09-02 on John's word):+# Every ship lands on the PRIVATE page adom/pup-bridge-insiders. Desktops whose wiki login can+# read that page (ab >= 2.0.99 probes its manifest first with the user's own session) follow it+# automatically; everyone else keeps following the PUBLIC manifest on adom/pup-bridge, which this+# script never touches. The public tier moves only via tools/promote.sh, which REFUSES without+# the human's exact words and records them in the public manifest. One plain semver per build+# in both tiers; promote moves a version, it never renames one. The skills pkg publishes at+# PROMOTE time (registry dist-tag tiering is adom/wiki#168), so insiders ships are runtime-only.+#+# Steps: verify the tree parses -> bump BRIDGE_VERSION + bridge.json + package.json (lockstep) ->+# zip src/ at the zip ROOT (no node_modules) -> release create/upload on the INSIDERS page ->+# write the insiders manifest with the asset's REAL sha256/size -> archive zip+manifest under+# dist/<ver>/ (promote + rollback work from this record) -> repo push the text files to the+# insiders page -> DIFF-VERIFY the version actually landed. # # Env: FORCE_PUSH=1 pushes even when the working tree looks unchanged.+# TIER=public is NOT accepted here on purpose — use tools/promote.sh. set -euo pipefail-VER="${1:?usage: tools/ship.sh <version> \"<changelog>\" [publish_pkg]}"+VER="${1:?usage: tools/ship.sh <version> \"<changelog>\"}" LOG="${2:?changelog sentence required}"-# pup-bridge#66 (2026-08-23): this defaulted to 0 AND every ship call passed 0 explicitly, so no ship-# since 2.0.276 published the skillpack while release-lockstep minted an empty version row each-# time: the top row had no tarball and `pkg install` 404'd. Lockstep means BOTH artifacts, every-# ship. Publishing is now the default; pass 0 only for a deliberate runtime-only test cut.-PUBLISH_PKG="${3:-1}"+if [ "${3:-}" != "" ] && [ "${3:-}" != "1" ]; then echo "note: the old PUBLISH_PKG arg is ignored — the skills pkg now publishes at promote time (tools/promote.sh)"; fi REPO="$(cd "$(dirname "$0")/.." && pwd)"-PAGE="adom/pup-bridge"+PAGE="adom/pup-bridge-insiders"+MANIFEST="adom-bridge-puppeteer-insiders-manifest.json" ZIP="adom-bridge-puppeteer-v${VER}.zip"+ARCH="dist/${VER}" cd "$REPO" echo "== 0. static gates ==" node --check src/server.js+node --check src/chrome.js+node --check src/card_vault.js node tests/check_dashboard_script.js >/dev/null+node tests/check_card_vault.js >/dev/null # 2.0.339: the dock moved to the bridge-served dashboard and the tab reverted to the old filled # tile, because the favicon belongs to the PAGE, not the app. This gate keeps the two copies of # the drawing identical and refuses currentColor, which renders black in an isolated document. node tests/check_icon.js >/dev/null-echo " server.js + served dashboard script parse; dock favicon matches the served icon"+echo " server/chrome/card_vault parse; dashboard script parses; card vault checks; dock favicon matches" echo "== 1. version lockstep ==" printf '%s' "$VER" > src/BRIDGE_VERSION@@ -41,19 +56,21 @@ v = sys.argv[1] p = 'src/bridge.json' d = json.load(open(p)) d['version'] = v+assert d.get('insidersManifestUrl'), 'bridge.json must declare insidersManifestUrl (pup-bridge#86)' json.dump(d, open(p, 'w'), indent=2) open(p, 'a').write('\n') print(f" src/BRIDGE_VERSION + src/bridge.json -> {v}")-p2 = 'package.json' # the SKILLPACK descriptor — pkg publish reads THIS version. Left unbumped-d2 = json.load(open(p2)) # from 2.0.277-2.0.293, every ship's pkg publish silently no-oped and-d2['version'] = v # `pkg install` 404'd on the empty top version row (pup-bridge#66).+p2 = 'package.json' # the SKILLPACK descriptor — pkg publish (at promote time) reads THIS version.+d2 = json.load(open(p2))+d2['version'] = v json.dump(d2, open(p2, 'w'), indent=2) open(p2, 'a').write('\n') print(f" package.json (skillpack) -> {v}") PY echo "== 2. build the zip (src/ at zip root, no node_modules) =="-python3 - "$ZIP" <<'PY'+mkdir -p "$ARCH"+python3 - "$ARCH/$ZIP" <<'PY' import zipfile, os, hashlib, sys out = sys.argv[1] with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) as z:@@ -65,61 +82,46 @@ with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) as z: print(f" {out} sha256={hashlib.sha256(open(out,'rb').read()).hexdigest()} size={os.path.getsize(out)}") PY -echo "== 3. release =="+echo "== 3. INSIDERS release ($PAGE) ==" adom-wiki release create "$PAGE" "$VER" >/dev/null 2>&1 || true-UP=$(adom-wiki release upload "$PAGE" "$VER" "$ZIP" --json 2>&1 | tail -c 4000)-python3 - "$UP" "$VER" "$ZIP" <<'PY'-import json, sys, re, datetime-raw, ver, zipname = sys.argv[1], sys.argv[2], sys.argv[3]+UP=$(adom-wiki release upload "$PAGE" "$VER" "$ARCH/$ZIP" --json 2>&1 | tail -c 4000)+python3 - "$UP" "$VER" "$ZIP" "$ARCH" "$MANIFEST" <<'PY'+import json, sys, datetime, hashlib, os+raw, ver, zipname, arch, manifest = sys.argv[1:6] i = raw.find('{') d = json.loads(raw[i:]) if i >= 0 else {} a = d.get('data') or d.get('asset') or d sha, size = a.get('sha256'), a.get('size')-url = a.get('download_url') or f"https://wiki.adom.inc/download/adom/pup-bridge/{ver}/{zipname}"+url = a.get('download_url') or f"https://wiki.adom.inc/download/adom/pup-bridge-insiders/{ver}/{zipname}" if not url.startswith('http'): url = 'https://wiki.adom.inc' + url+zp = os.path.join(arch, zipname) if not (sha and size): # fall back to the local file (same bytes we just uploaded)- import hashlib, os- sha = hashlib.sha256(open(zipname, 'rb').read()).hexdigest(); size = os.path.getsize(zipname)-m = json.load(open('adom-bridge-puppeteer-manifest.json'))-m.update({'version': ver, 'url': url, 'sha256': sha, 'size': size,- 'released_at': datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')})-json.dump(m, open('adom-bridge-puppeteer-manifest.json', 'w'), indent=2)-open('adom-bridge-puppeteer-manifest.json', 'a').write('\n')-print(f" manifest -> {url}\n sha256={sha} size={size}")+ sha = hashlib.sha256(open(zp, 'rb').read()).hexdigest(); size = os.path.getsize(zp)+# The insiders manifest is the same SHAPE as the public one; seed it from the public manifest the+# first time so name/entrypoint/etc. stay identical between tiers.+base = json.load(open(manifest)) if os.path.exists(manifest) else json.load(open('adom-bridge-puppeteer-manifest.json'))+for k in ('promotion_attestation', 'promoted_by', 'promoted_at', 'rollback_attestation', 'rolled_back_by', 'rolled_back_at', 'insiders_released_at'):+ base.pop(k, None)+base.update({'version': ver, 'url': url, 'sha256': sha, 'size': size, 'tier': 'insiders',+ 'released_at': datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')})+json.dump(base, open(manifest, 'w'), indent=2); open(manifest, 'a').write('\n')+json.dump(base, open(os.path.join(arch, 'insiders-manifest.json'), 'w'), indent=2)+print(f" insiders manifest -> {url}\n sha256={sha} size={size}\n archived {arch}/ (zip + insiders-manifest.json)") PY -# package.json was MISSING from this list until 2.0.355, so the Files tab sat at 2.0.353 and did-# not show the agent_permissions block at all. The pkg TARBALL was always correct, which is why-# nothing broke and nobody noticed: the stale copy is the one humans read, not the one installs use.-echo "== 4. push the text files =="+echo "== 4. push the text files to the INSIDERS page ==" adom-wiki repo push "$PAGE" \- --files adom-bridge-puppeteer-manifest.json src/bridge.json src/BRIDGE_VERSION src/server.js src/chrome.js SKILL.md package.json \- -m "v${VER}: ${LOG}" >/dev/null 2>&1 || {+ --files "$MANIFEST" src/bridge.json src/BRIDGE_VERSION src/*.js src/extension/*.js src/extension/*.json SKILL.md package.json \+ -m "v${VER} (insiders): ${LOG}" >/dev/null 2>&1 || { [ "${FORCE_PUSH:-0}" = "1" ] || { echo " PUSH FAILED (set FORCE_PUSH=1 to retry)"; exit 1; } } -if [ "$PUBLISH_PKG" = "1" ]; then- echo "== 4b. pkg publish =="- # pup-bridge#66: publish VISIBLY and treat failure as failure — the silent || swallow hid 17- # versions of no-oped publishes (descriptor version never bumped) while release-lockstep created- # EMPTY pkg version rows, so `pkg install` resolved the top row and 404'd on the missing tarball.- PUBOUT=$(adom-wiki pkg publish --org adom 2>&1); PUBRC=$?- echo "$PUBOUT" | tail -3- [ $PUBRC -eq 0 ] || { echo "PKG PUBLISH FAILED (exit $PUBRC) — the top version row will have no tarball and pkg install will 404 (see pup-bridge#66)"; echo "$PUBOUT" | tail -15; exit 1; }- # belt-and-suspenders: confirm the registry's top row actually carries a tarball- adom-wiki pkg info adom/pup-bridge 2>/dev/null | python3 -c "-import sys,json-raw=sys.stdin.read()-d,_=json.JSONDecoder().raw_decode(raw[raw.find('{'):])-v=d['versions'][0]-assert v.get('integrity') and v.get('size_bytes'), f\"top row {v['version']} has NO tarball\"-print(f\" pkg tarball verified on top row {v['version']} ({v['size_bytes']} bytes)\")" || { echo "PKG TARBALL VERIFY FAILED"; exit 1; }-fi- echo "== 5. DIFF-VERIFY (a no-op ship is the classic silent failure) ==" ON_DISK=$(cat src/BRIDGE_VERSION)-MAN=$(python3 -c "import json;print(json.load(open('adom-bridge-puppeteer-manifest.json'))['version'])")+MAN=$(python3 -c "import json,sys;print(json.load(open(sys.argv[1]))['version'])" "$MANIFEST") BJ=$(python3 -c "import json;print(json.load(open('src/bridge.json'))['version'])") [ "$ON_DISK" = "$VER" ] && [ "$MAN" = "$VER" ] && [ "$BJ" = "$VER" ] \- && echo "VERIFIED $VER on disk (BRIDGE_VERSION + bridge.json + manifest in lockstep)" \+ && echo "VERIFIED $VER on INSIDERS (BRIDGE_VERSION + bridge.json + insiders manifest in lockstep)" \ || { echo "LOCKSTEP BROKEN: disk=$ON_DISK bridge.json=$BJ manifest=$MAN"; exit 1; }+echo " PUBLIC tier untouched. To roll this out to everyone, on the human's words:"+echo " tools/promote.sh $VER \"<their exact words>\" --by \"<name>\""