skills: record the vendored snapshot's provenance, and report which copy wins
Found while verifying v1.8.7 from inside a fresh container: the baked mempalace
snapshot is read by no host on this fleet. devbox-skill-reconcile repoints
~/.agents/skills/mempalace at the mounted live clone (the v1.8.5 fix working as
designed), and all four compose stacks mount a workspace containing the
skillset. So the phrase canary that blocked v1.8.7's first tag polices a file
nobody opens, while the drift that could actually mislead an agent — a git pull
nobody ran in /workspace/skillset — was invisible from inside the container and
is invisible to CI by construction.
Record provenance instead of policing it, and move the check to where the
skillset actually is:
- Dockerfile.variant: ARG SKILLSET_SNAPSHOT_REF (the claim) + a sha256 of the
shipped bytes measured in the manifest layer (the fact), as manifest siblings
rather than components{} members, plus an OCI label. An ARG default, not a
CI-resolved output: no credential for the private skillset, no change at any
of the four variant build call sites, and a local docker build records what CI
does. Variant-only, so no base rebuild — check-base-hash.sh scans
Dockerfile.base alone, verified by running it.
- pi-devbox-version: a skills: section naming baked vs live <repo> @ <sha> per
vendored skill, and for mempalace whether the live copy is identical to the
baked fingerprint, at the same commit with uncommitted edits, or divergent.
entrypoint-user.sh passes the new --no-skills, because the banner prints
before the links exist and long before the reconcile runs.
- scripts/vendor-mempalace-skill.sh: refresh the file and rewrite the ref
together (a cp without an ARG bump makes the manifest lie, which is worse than
anonymity); --check verifies the claim against a real clone.
- 5 new smoke assertions (78 -> 83), mutation-tested through the real sh -c
path: 6 fabricated manifests, where a well-formed hash of the wrong file
proves the two manifest assertions are not redundant; the all-baked reporting
test verified to FAIL against a live-skillset environment.
Reviewed mid-flight by pi@emb-7kj4vr4g over the logstream (correlation
skillset-vendor-drift), which retracted its own earlier recommendation of a
build-time byte-compare against skillset HEAD and supplied the better framing:
the invariant is NON-CONTRADICTION, not currency. Byte parity on a fallback
would have cost a resync commit plus a ~67-min base rebuild for each of the four
skillset commits pushed in one evening. Its warning also found a real bug here:
the script now CONSTRUCTS the snapshot from `git show HEAD:<path>` instead of
copying the working tree, because a clean `git diff` says nothing about an
untracked file — the one input the first draft would have recorded a false ref
for. Tested: untracked, unstaged and staged-but-uncommitted all refuse, atomically.
Also fixes three stale in-repo markers of the same class the canary belongs to
(true when written, silently false at release): two dangling "Unreleased"
pointers and a typst line still marked Unreleased five releases after v1.4.0.
This commit is contained in:
+54
-1
@@ -277,6 +277,36 @@ ARG SOURCE_REVISION=
|
||||
# MEMPALACE_TOOLKIT_REF is consumed in Dockerfile.base; re-declared here
|
||||
# only so its intended ref lands in the label set alongside the others.
|
||||
ARG MEMPALACE_TOOLKIT_REF=main
|
||||
# ── Vendored skill provenance ─────────────────────────────────────────
|
||||
# The vendored mempalace SKILL.md is the ONLY baked artefact with no /opt
|
||||
# clone behind it: its upstream (the skillset repo) is PRIVATE, so the
|
||||
# image cannot clone it and CI cannot resolve its HEAD (see VENDORED.md).
|
||||
# Consequence through v1.8.7: the snapshot was ANONYMOUS — nothing in the
|
||||
# image or the repo recorded which skillset commit it was taken from, so
|
||||
# the only staleness check available was a hand-maintained phrase canary in
|
||||
# scripts/smoke-test.sh, which by construction can only detect "older than
|
||||
# what I remembered to pin", never "older than skillset main".
|
||||
#
|
||||
# Recording the ref costs nothing and makes the question answerable. It is
|
||||
# deliberately a plain ARG DEFAULT rather than a CI-resolved output:
|
||||
# * the value is a fact about the committed snapshot, so it belongs in
|
||||
# the tree next to it — not in a workflow that a local `docker build`
|
||||
# never runs (same reasoning as MEMPALACE_VERSION living in
|
||||
# Dockerfile.base rather than being duplicated in docker-publish.yml);
|
||||
# * CI therefore needs NO new build-arg at any of its four
|
||||
# Dockerfile.variant call sites (smoke, smoke-studio, build-variant,
|
||||
# build-variant-studio) — a plumbing change that is easy to
|
||||
# under-apply to only two of them;
|
||||
# * and it needs no credential for a private repo.
|
||||
# Bump it with scripts/vendor-mempalace-skill.sh, which refreshes the file
|
||||
# and rewrites this line together, so the pair cannot drift apart by hand.
|
||||
# This ARG lives in Dockerfile.variant ON PURPOSE: Dockerfile.base and
|
||||
# rootfs/ are both hashed into base_tag, so recording provenance here costs
|
||||
# no ~67-minute base rebuild. (scripts/check-base-hash.sh scans only
|
||||
# Dockerfile.base, so no folding into the base hash is required — nor would
|
||||
# it be correct, since this ARG changes nothing about the base's contents.)
|
||||
ARG SKILLSET_SNAPSHOT_REF=c04cd156592decf8258cfce7aa5e123ac0a5c7e2
|
||||
|
||||
# Dockerfile.base sets description="pi-devbox — base image (variant-independent)"
|
||||
# and every variant INHERITS it, so both published images used to advertise
|
||||
# themselves on Docker Hub as the base image. A LABEL cannot branch on
|
||||
@@ -301,7 +331,8 @@ LABEL org.opencontainers.image.version="${RELEASE_TAG}" \
|
||||
se.jordbo.pi-devbox.pi-atelier-version="${PI_ATELIER_VERSION}" \
|
||||
se.jordbo.pi-devbox.mempalace-toolkit-ref="${MEMPALACE_TOOLKIT_REF}" \
|
||||
se.jordbo.pi-devbox.pi-studio-ref="${PI_STUDIO_REF}" \
|
||||
se.jordbo.pi-devbox.pi-studio-version="${PI_STUDIO_VERSION}"
|
||||
se.jordbo.pi-devbox.pi-studio-version="${PI_STUDIO_VERSION}" \
|
||||
se.jordbo.pi-devbox.skillset-snapshot-ref="${SKILLSET_SNAPSHOT_REF}"
|
||||
|
||||
# The manifest is written from GROUND TRUTH — the actual checked-out HEAD
|
||||
# of each /opt clone and the live `pi --version` — not merely the intended
|
||||
@@ -327,6 +358,20 @@ RUN set -e; \
|
||||
case "$MP_V" in [0-9]*) MP_CORE="\"${MP_V}\"" ;; *) MP_CORE='null' ;; esac; \
|
||||
STUDIO_REV='null'; \
|
||||
if [ -d /opt/pi-studio/.git ]; then STUDIO_REV="\"$(rev /opt/pi-studio)\""; fi; \
|
||||
# The vendored skill snapshot's fingerprint is MEASURED here, not passed
|
||||
# in as a build-arg, per the ground-truth rule above: SKILLSET_SNAPSHOT_REF
|
||||
# is a CLAIM about which skillset commit the file came from, while this
|
||||
# hash is what the image actually ships. Recorded together they let any
|
||||
# reader with the skillset checked out — which on this fleet is every
|
||||
# host, since all four compose stacks mount it — verify the claim at
|
||||
# RUNTIME, without CI ever needing access to the private repo. Degrades
|
||||
# to JSON null rather than failing the build if the file is absent; the
|
||||
# smoke assertion is what turns that into a loud failure.
|
||||
SKILL_SNAP='null'; \
|
||||
_snap_file=/usr/local/share/pi-devbox/skills/mempalace/SKILL.md; \
|
||||
if [ -f "$_snap_file" ]; then \
|
||||
SKILL_SNAP="\"$(sha256sum "$_snap_file" | cut -d' ' -f1)\""; \
|
||||
fi; \
|
||||
{ \
|
||||
echo '{'; \
|
||||
echo " \"release_tag\": \"${RELEASE_TAG}\","; \
|
||||
@@ -337,6 +382,14 @@ RUN set -e; \
|
||||
# SHAs and `pi-devbox-version` renders it with .value[0:12], which would
|
||||
# silently truncate a longer version string.
|
||||
echo " \"mempalace_version\": ${MP_CORE},"; \
|
||||
# Siblings, NOT members of components{}, for two independent reasons:
|
||||
# that map means "HEAD of a clone present in this image" and the
|
||||
# skillset is not cloned here (calling it a component would be a
|
||||
# lie a future reader would act on), and `pi-devbox-version` renders
|
||||
# every components{} value with .value[0:12] — which would truncate
|
||||
# a 64-hex sha256 into something that looks like a short commit.
|
||||
echo " \"skillset_snapshot_ref\": \"${SKILLSET_SNAPSHOT_REF}\","; \
|
||||
echo " \"skillset_snapshot_sha256\": ${SKILL_SNAP},"; \
|
||||
echo " \"components\": {"; \
|
||||
echo " \"pi-toolkit\": \"$(rev /opt/pi-toolkit)\","; \
|
||||
echo " \"pi-extensions\": \"$(rev /opt/pi-extensions)\","; \
|
||||
|
||||
Reference in New Issue
Block a user