skills: a gate that could pass without checking, and a mailbox that never empties
Fixes the three blockers and seven should-fixes from pi@emb-7kj4vr4g's review (logstream correlation skills-provenance-review, full text in drawer_pi-devbox_reviews_e43e766641c9ec85217bc6ce). Every finding was reproduced by execution here before being fixed; two were refined by that reproduction rather than taken as given. BLOCKER 1 — the provenance gate could print OK and exit 0 without verifying. `git show <ref>:<path> | sha256sum` hashes EMPTY STDIN when the ref does not resolve, so at_ref was never empty and the UNKNOWN branch was dead code. Measured: a bogus ref reported MISMATCH — accusing the snapshot of lying when the real cause was an incomplete clone, and the operator's natural remedy for MISMATCH is to re-run the refresh, which rewrites provenance to silence the complaint; and with a 0-byte snapshot against a 0-byte upstream file it printed "OK: exactly skillset@aaaaaaa" with exit 0 for a ref that does not exist. The script already had the sha_empty idiom and had applied it to blob_sha but not to at_ref. Existence is now PROVEN with git cat-file -e before anything is hashed, at two levels (ref resolves / path exists at it) because those deserve different messages. Same defect class as the canary it replaces: a check that can succeed without checking. A second, unflagged instance of the same pipeline shape in blob_sha was found and fixed too. Exit codes split, because the old contract failed the sanctioned case: 0 truthful (including stale, with a NOTICE), 1 a lying record only, 2 cannot determine. AGENTS.md step 2 promised "the message distinguishes the two" and was the thing this branch was breaking; rewritten to state all three. BLOCKER 3 — VENDORED.md contradicted itself in the release whose stated invariant is non-contradiction: its hand-maintained provenance line named skillset 670f7f1, seven commits behind the ARG and itself the commit that told agents to hand-stamp added_by — the withdrawn instruction this work exists to stop shipping — while its cp recipe contradicted the "not cp" rule 20 lines above. Line removed (nothing forced it to move when the ARGs did); 670f7f1 kept only as a labelled cautionary example. The pi-extensions half was verified redundant (CI require_sha resolves PI_EXTENSIONS_REF) before removal. SHOULD-FIXES: `<root> --check`, the spelling VENDORED.md documented, silently ran a REFRESH because only $1 was parsed (both tools now parse all args and reject unknown ones); refresh at a detached/older HEAD silently rewound ref and bytes (now refused unless the recorded ref is an ancestor, --force to override); upstream_dirty was computed and never used in check mode; --no-skills --json printed human text and broke jq; --help was a hardcoded sed range this branch had already made stale; the fingerprint hashed SKILL.md alone so a live skill differing only in a sibling file reported "identical", and pi-extensions already ships two files, so it is now a per-skill TREE hash with the manifest field renamed skillset_snapshot_tree_sha256; the --no-skills smoke assertion was negative-only and passed on a crashed binary. mktemp+mv left files 0600 — CI was unaffected since the index records 100644, so the blast radius was local builds only, narrower than the review inferred. Snapshot resynced c04cd15 -> 5fd0d5c so the no-clone fallback carries the CORRECTED coordination protocol rather than the withdrawn one; --check is now OK with no staleness notice, and the bidirectional canary re-verified against the new bytes. Local validation is bash -n only (shellcheck, hadolint and actionlint are all absent in this container) — CI remains the shellcheck gate.
This commit is contained in:
+23
-7
@@ -305,7 +305,7 @@ ARG MEMPALACE_TOOLKIT_REF=main
|
||||
# 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
|
||||
ARG SKILLSET_SNAPSHOT_REF=5fd0d5c406df506fd0e70977b6bd87f5fbc3b803
|
||||
|
||||
# Dockerfile.base sets description="pi-devbox — base image (variant-independent)"
|
||||
# and every variant INHERITS it, so both published images used to advertise
|
||||
@@ -365,12 +365,26 @@ RUN set -e; \
|
||||
# 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.
|
||||
# to JSON null rather than failing the build if the directory is absent;
|
||||
# the smoke assertion is what turns that into a loud failure.
|
||||
#
|
||||
# Hashes the whole DIRECTORY, not just SKILL.md: a single-file hash
|
||||
# answers "did this one file change", not "is the live copy the same
|
||||
# skill" — a live checkout that added or edited a SIBLING file (a
|
||||
# reference/ doc, a helper script) would still report "identical to
|
||||
# baked snapshot" against a file-only hash. pi-extensions already ships
|
||||
# two files for exactly this reason (SKILL.md + evaluate-extension-usage.py),
|
||||
# so this is not a hypothetical. Deterministic over `find | sort`, never
|
||||
# readdir order: relative paths + per-file sha256, folded into one hash.
|
||||
# pi-devbox-version mirrors this exact pipeline over the live directory so
|
||||
# the two sides are comparable — if you change this, change that too.
|
||||
tree_sha256() { \
|
||||
( cd "$1" && find . -type f -print | LC_ALL=C sort | xargs -r sha256sum ) 2>/dev/null | sha256sum | cut -d' ' -f1; \
|
||||
}; \
|
||||
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)\""; \
|
||||
_snap_dir=/usr/local/share/pi-devbox/skills/mempalace; \
|
||||
if [ -d "$_snap_dir" ] && [ -n "$(find "$_snap_dir" -type f -print -quit)" ]; then \
|
||||
SKILL_SNAP="\"$(tree_sha256 "$_snap_dir")\""; \
|
||||
fi; \
|
||||
{ \
|
||||
echo '{'; \
|
||||
@@ -388,8 +402,10 @@ RUN set -e; \
|
||||
# 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.
|
||||
# Named `_tree_sha256`, not `_sha256`: it measures every file under the
|
||||
# vendored skill directory, not one file — see tree_sha256() above.
|
||||
echo " \"skillset_snapshot_ref\": \"${SKILLSET_SNAPSHOT_REF}\","; \
|
||||
echo " \"skillset_snapshot_sha256\": ${SKILL_SNAP},"; \
|
||||
echo " \"skillset_snapshot_tree_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