Files
pi-devbox/scripts/vendor-mempalace-skill.sh
T
joakimp 49a6534093
Lint / hadolint (push) Successful in 13s
Lint / actionlint (push) Successful in 15s
docs: write down the coordination channel the fleet already runs on
The logstream has carried cross-machine work since 2026-08-18 — patch handoff,
review, a v1->v2 supersede — and nothing in this repo said it existed. That gap
had a measurable cost this morning: another host addressed a retraction to
pi@tor-ms22 by name and it was read only because the human said "read the
logstream", while the agent was actively rebuilding the thing it warned about.

Split by what each document is authoritative for, so there is one copy of each
claim rather than three that drift:

- README § Cross-machine agent coordination — what the CONTAINER needs.
  MEMPALACE_REMOTE_URL selects the shared palace; MEMPALACE_PI_DEVICE is what
  makes this machine reachable, because where every host is a thin client of one
  palace the stamped agent name is the only thing that distinguishes them. Stated
  as a rule with teeth: set both or neither, since a container missing the device
  var can read the log but is addressable by nobody.
- AGENTS.md release checklist step 2 — the vendored-snapshot refresh, as a
  MECHANISM in the document a releasing agent actually reads, not a comment
  hoping to be noticed. It says the refresh costs a base rebuild, that skipping
  it is legitimate (every enrolled host reads its live clone), and that skipping
  it silently is not.
- CHANGELOG — the three-way split itself, plus the measurement that shaped the
  ack contract: unfiltered, the mailbox returned 5 events, 4 of them finished
  broadcasts from eight days earlier; with status="open", exactly the 1 that
  needed an answer.

Norms live in the skillset skill (82a8d3c, already live on every host that mounts
the skillset — no rebuild) and mechanism in mempalace-toolkit's
extensions/pi/README.md (e70bef2, which also documents the edge stamper that
553d8657 shipped undocumented). Deliberately NOT duplicated here.

Consequence recorded rather than hidden: the skill edit lands in the skillset, so
this repo's SKILLSET_SNAPSHOT_REF now honestly reports itself behind, and
--check exits 1 with "has moved to 82a8d3c; the snapshot describes the older
c04cd15". That message is also fixed in this commit — it previously blamed "the
working tree" even when the tree was clean and only the ref had moved, which is
the same defect class as a canary pinned to a phrase the release deleted: a
message that names the wrong cause. Now distinguishes moved-HEAD from dirty-tree,
verified against both plus the in-sync case.
2026-08-26 12:51:26 +02:00

168 lines
7.6 KiB
Bash
Executable File

#!/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@<recorded ref>?"
# — 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:-<ref not present in this clone>}"
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'