master
John Lauer promote.sh stamps 'Promoted to public on <date>' into the release notes (pup-bridge#87 item 2) 6738513 2mo ago
#!/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 manifest (adom-bridge-puppeteer-manifest.json,
# the updateManifestUrl every non-insider desktop follows) 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. The words travel IN the public manifest
# (promotion_attestation / promoted_by / promoted_at).
#
# The bytes are ALREADY on the product page: tools/ship.sh uploaded the zip as release <ver> on
# adom/pup-bridge and archived it under dist/<ver>/ with the insiders manifest. Promote just
# re-points the public manifest at that same asset (sha re-checked) and pushes it. Source + skills
# pkg were pushed/published at ship time.
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"
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: $VER 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"; exit 1; }
URL=$(python3 -c "import json,sys;print(json.load(open(sys.argv[1]))['url'])" "$ARCH/insiders-manifest.json")
REMOTE=$(curl -sL "$URL" | python3 -c "import hashlib,sys;print(hashlib.sha256(sys.stdin.buffer.read()).hexdigest())")
[ "$REMOTE" = "$SHA_LOCAL" ] || { echo "REMOTE ASSET MISMATCH: $URL serves sha $REMOTE, archive is $SHA_LOCAL — refusing to point the public tier at it"; exit 1; }
echo "   $ZIP sha256=$SHA_LOCAL matches the insiders record AND the served release asset"

echo "== 2. public manifest =="
python3 - "$VER" "$ARCH" "$A" "$BY" <<'PY'
import json, sys, datetime, os
ver, arch, words, by = sys.argv[1:5]
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': ins['url'], 'sha256': ins['sha256'], 'size': ins['size'], 'released_at': now,
          'insiders_released_at': ins.get('released_at'),
          'promotion_attestation': words, 'promoted_by': by, 'promoted_at': now})
m.pop('tier', None)
for k in ('rollback_attestation', 'rolled_back_by', 'rolled_back_at'): m.pop(k, 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 -> {ins['url']}\n   archived {arch}/public-manifest.json")
PY

echo "== 3a. mark the insiders release-log entry promoted =="
python3 - "dist/insiders-repo/pup-bridge/README.md" "$VER" "$A" "$BY" <<'PY'
import sys, datetime, re, os
path, ver, words, by = sys.argv[1:5]
if os.path.exists(path):
    s = open(path).read()
    pat = re.compile(r"(- \*\*" + re.escape(ver) + r"\*\* insiders, [0-9-]+\.)")
    if pat.search(s) and 'Promoted to public' not in pat.search(s).group(0):
        s = pat.sub(lambda m: m.group(1) + f" Promoted to public on {datetime.date.today().isoformat()} by {by}: \"{words}\".", s, count=1)
        # the insiders-tier-publish convention: the entry says "Not promoted." until this moment
        line_start = s.find(f"- **{ver}**"); line_end = s.find("\n", line_start)
        s = s[:line_start] + s[line_start:line_end].replace(" Not promoted.", "") + s[line_end:]
        open(path, 'w').write(s); print(f"   release log: {ver} marked promoted")
    else: print(f"   release log: no insiders entry for {ver} to mark (or already marked)")
PY
( cd dist/insiders-repo && adom-wiki repo push adom/insiders --files pup-bridge/README.md -m "pup-bridge ${VER}: promoted to public — ${BY}" >/dev/null 2>&1 ) || echo "   (insiders release-log push failed; the public promote continues)"

echo "== 3b. stamp the release notes on the product page (pattern: 'Promoted to public on <date>') =="
NOTES=$(adom-wiki release view "$PUBLIC" "$VER" --json 2>/dev/null | python3 -c "import sys,json; raw=sys.stdin.read(); i=raw.find('{'); d=json.loads(raw[i:]) if i>=0 else {}; r=d.get('data') or d; r=r.get('release') or r; print((r.get('changelog') or '').strip())" 2>/dev/null || true)
case "$NOTES" in *"Promoted to public"*) echo "   release notes already stamped";; *)
  adom-wiki release edit "$PUBLIC" "$VER" --changelog "${NOTES:+$NOTES }Promoted to public on $(date -u +%Y-%m-%d)." >/dev/null 2>&1 && echo "   release notes stamped" || echo "   (release notes stamp failed; promote continues)";; esac

echo "== 3. push the public manifest =="
adom-wiki repo push "$PUBLIC" --files adom-bridge-puppeteer-manifest.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. DIFF-VERIFY =="
MAN=$(python3 -c "import json;print(json.load(open('adom-bridge-puppeteer-manifest.json'))['version'])")
LIVE=$(curl -s https://wiki.adom.inc/api/v1/pages/pup-bridge/files/adom-bridge-puppeteer-manifest.json | python3 -c "import json,sys;print(json.load(sys.stdin).get('version'))" 2>/dev/null || echo '?')
[ "$MAN" = "$VER" ] && echo "PROMOTED $VER to PUBLIC (attested by $BY); live public manifest reads $LIVE" || { echo "PUBLIC MANIFEST NOT AT $VER (got $MAN)"; exit 1; }