#!/usr/bin/env bash # vendor-mempalace-skill.sh — refresh the vendored mempalace skill snapshot # AND its recorded provenance, together, so the two cannot drift apart. # # WHY THIS EXISTS # --------------- # rootfs/usr/local/share/pi-devbox/skills/mempalace/SKILL.md is a snapshot of a # file owned by the PRIVATE skillset repo (see VENDORED.md). Because the image # cannot clone that repo, refreshing the snapshot was a manual `cp` — and the # result was anonymous: nothing recorded WHICH skillset commit the bytes came # from. The only staleness check available was a hand-maintained phrase canary # in scripts/smoke-test.sh, which by construction detects "older than the phrase # I remembered to pin", never "older than skillset main". # # Two facts now travel with the snapshot: the skillset commit it was taken from # (ARG SKILLSET_SNAPSHOT_REF in Dockerfile.variant) and the sha256 of the bytes # themselves (measured at build time into build-manifest.json). This script is # the only thing that should ever write the first one, because a `cp` without a # matching ARG bump produces a manifest that CONFIDENTLY LIES — worse than the # anonymous snapshot it replaced. # # USAGE # scripts/vendor-mempalace-skill.sh [skillset-root] refresh + record # scripts/vendor-mempalace-skill.sh --check [root] verify, write nothing # # skillset-root defaults to /workspace/skillset, then $HOME/skillset. # # --check answers "is the committed snapshot really skillset@?" # — the question CI cannot answer without a credential for the private repo, # and which anyone with the skillset checked out can answer for free. Exit 0 # when the snapshot is honest and current, 1 when it is not. # # EXIT STATUS # 0 refreshed (or already up to date; --check: snapshot verified) # 1 refused: dirty upstream file, missing repo, or --check mismatch set -euo pipefail cd "$(dirname "$0")/.." DOCKERFILE="Dockerfile.variant" VENDORED="rootfs/usr/local/share/pi-devbox/skills/mempalace/SKILL.md" ARG_NAME="SKILLSET_SNAPSHOT_REF" REL_PATH="skills/mempalace/SKILL.md" MODE="refresh" if [ "${1:-}" = "--check" ]; then MODE="check" shift fi ROOT="${1:-}" if [ -z "$ROOT" ]; then for candidate in /workspace/skillset "$HOME/skillset"; do if [ -d "$candidate/.git" ]; then ROOT="$candidate" break fi done fi die() { printf '%s: %s\n' "$(basename "$0")" "$1" >&2; exit 1; } [ -n "$ROOT" ] || die "no skillset clone found (pass one: $(basename "$0") /path/to/skillset)" [ -d "$ROOT/.git" ] || die "not a git clone: $ROOT" [ -f "$ROOT/$REL_PATH" ] || die "no $REL_PATH in $ROOT" [ -f "$VENDORED" ] || die "vendored snapshot missing: $VENDORED" head_sha=$(git -C "$ROOT" rev-parse HEAD 2>/dev/null) || die "cannot read HEAD of $ROOT" recorded=$(grep -oE "^ARG ${ARG_NAME}=[0-9a-f]{40}$" "$DOCKERFILE" | cut -d= -f2 || true) [ -n "$recorded" ] || die "no 'ARG ${ARG_NAME}=<40-hex>' line in $DOCKERFILE" sha_of() { sha256sum "$1" | cut -d' ' -f1; } sha_empty=$(printf '' | sha256sum | cut -d' ' -f1) vendored_sha=$(sha_of "$VENDORED") upstream_sha=$(sha_of "$ROOT/$REL_PATH") # The recorded ref must PROVABLY describe the recorded bytes. Reviewed by # pi@emb-7kj4vr4g (logstream correlation skillset-vendor-drift, 2026-08-26): # "make sure the resync script writes the ref it ACTUALLY copied from, or the # provenance field inherits the same class of bug the canary just had." So this # does not copy the working tree and then hope a `git diff` was enough — a # clean-looking `git diff` says nothing about an UNTRACKED file, and a detached # or behind checkout can be clean while HEAD names something else. Instead the # snapshot is CONSTRUCTED from the ref being recorded (below), and the working # tree is compared only so a local edit produces a refusal rather than a silent # surprise. Order matters here: everything this block reads is defined above it. blob_sha=$(git -C "$ROOT" show "HEAD:$REL_PATH" 2>/dev/null | sha256sum | cut -d' ' -f1 || true) upstream_dirty="" if [ -z "$blob_sha" ] || [ "$blob_sha" = "$sha_empty" ]; then upstream_dirty="not present at HEAD (untracked, or absent at this commit)" elif [ "$blob_sha" != "$upstream_sha" ]; then if ! git -C "$ROOT" diff --quiet -- "$REL_PATH" 2>/dev/null; then upstream_dirty="modified but not committed" elif ! git -C "$ROOT" diff --cached --quiet -- "$REL_PATH" 2>/dev/null; then upstream_dirty="staged but not committed" else upstream_dirty="different at HEAD than in the working tree" fi fi if [ "$MODE" = "check" ]; then # Compare the committed snapshot against the file AT THE RECORDED REF, not # against the working tree: the question is whether the record is truthful, # which is independent of how current it is. Both are reported. at_ref=$(git -C "$ROOT" show "${recorded}:${REL_PATH}" 2>/dev/null | sha256sum | cut -d' ' -f1 || true) printf 'recorded ref: %s\n' "$recorded" printf 'vendored sha256: %s\n' "$vendored_sha" printf 'sha256 at ref: %s\n' "${at_ref:-}" printf 'skillset HEAD: %s (%s)\n' "$head_sha" "$(sha_of "$ROOT/$REL_PATH")" rc=0 if [ -z "$at_ref" ]; then printf 'UNKNOWN: %s is not in this clone — fetch, or check against a complete one\n' "$recorded" >&2 rc=1 elif [ "$at_ref" != "$vendored_sha" ]; then printf 'MISMATCH: the vendored snapshot is NOT the file at the recorded ref\n' >&2 rc=1 else printf 'OK: the vendored snapshot is exactly skillset@%s:%s\n' "${recorded:0:7}" "$REL_PATH" fi if [ "$vendored_sha" != "$upstream_sha" ]; then # Name the ACTUAL cause. "working tree differs" is wrong when the tree is # clean and the ref simply moved on — a message that names the wrong cause is # the same defect class as a canary pinned to a deleted phrase. if [ "$recorded" != "$head_sha" ] && [ "$blob_sha" = "$upstream_sha" ]; then printf 'STALE: %s has moved to %s; the snapshot describes the older %s\n' \ "$ROOT" "${head_sha:0:7}" "${recorded:0:7}" >&2 else printf 'STALE: the working tree of %s differs from the snapshot (HEAD %s)\n' \ "$ROOT" "${head_sha:0:7}" >&2 fi rc=1 fi exit "$rc" fi [ -z "$upstream_dirty" ] || die "$ROOT/$REL_PATH is $upstream_dirty — commit it first, or the recorded ref would not describe these bytes" if [ "$vendored_sha" = "$upstream_sha" ] && [ "$recorded" = "$head_sha" ]; then printf 'already current: snapshot == skillset@%s\n' "${head_sha:0:7}" exit 0 fi # Written FROM THE REF, not copied from the working tree, so the pair cannot be # a lie by construction. Via a temp file so a failed write cannot leave a # half-vendored snapshot behind. snap_tmp=$(mktemp) if ! git -C "$ROOT" show "HEAD:$REL_PATH" > "$snap_tmp" 2>/dev/null; then rm -f -- "$snap_tmp" die "cannot read HEAD:$REL_PATH from $ROOT" fi mv -- "$snap_tmp" "$VENDORED" [ "$(sha_of "$VENDORED")" = "$blob_sha" ] \ || die "internal: written snapshot does not match HEAD:$REL_PATH" # In-place, and only the exact pinned line: a broad sed on this Dockerfile could # rewrite one of the other *_REF ARGs. tmp=$(mktemp) sed "s|^ARG ${ARG_NAME}=.*$|ARG ${ARG_NAME}=${head_sha}|" "$DOCKERFILE" > "$tmp" mv -- "$tmp" "$DOCKERFILE" new_recorded=$(grep -oE "^ARG ${ARG_NAME}=[0-9a-f]{40}$" "$DOCKERFILE" | cut -d= -f2 || true) [ "$new_recorded" = "$head_sha" ] || die "failed to rewrite ${ARG_NAME} in $DOCKERFILE" printf 'snapshot: %s -> %s\n' "${vendored_sha:0:12}" "$(sha_of "$VENDORED" | cut -c1-12)" printf 'ref: %s -> %s\n' "${recorded:0:7}" "${head_sha:0:7}" printf '\nNOTE: %s is hashed into base_tag, so this costs a base rebuild\n' "$VENDORED" printf 'on the next tag (~67 min). Also re-pin the phrase canary in\n' printf 'scripts/smoke-test.sh if the section it names changed.\n'