Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ check-time oracle, not a source of truth `install.sh` consumes. If the installer
file, the question "does the inventory match what installs?" becomes unfalsifiable, because the
two would no longer be independent derivations of the same fact.

`tests/inventory-contract.sh --print-digest` is the only supported way to recompute
`derived_at.payload_digest` by hand -- it prints the value `payload_digest_compute()` produces and
nothing else, so checking a number against the file never means retyping the recipe.

`tests/inventory-contract.sh --write` (alias `--repoint`) is the fix for the recurring failure
this file goes stale with: a payload commit lands and `derived_at.head`/`derived_at.payload_digest`
still name the commit before it. It refuses on a dirty payload tree (commit the payload first --
the head must name a commit), otherwise sets `derived_at.head` to `git rev-parse HEAD` and
`derived_at.payload_digest` to `payload_digest_compute()`, writes the file back with jq (same key
order and indent already on disk), re-validates, and on a clean result prints the commit command to
run. Idempotent: running it again on an already-repointed clean tree changes nothing.

## What this proves

This setup's core property is that Claude Code skills fire on the situation
Expand Down
82 changes: 80 additions & 2 deletions tests/inventory-contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
#
# Not yet wired into .claude/verify.sh or CI (see tests/README.md). Run by hand:
# tests/inventory-contract.sh
#
# `--write` (alias `--repoint`) is the ONLY supported way to re-point derived_at.head and
# derived_at.payload_digest at HEAD after a payload commit. It refuses on a dirty payload tree,
# reuses payload_digest_compute() below rather than a second implementation, writes the file with
# jq (stable key order, same 2-space indent already on disk), re-validates, and on a clean result
# prints the commit command to run:
# tests/inventory-contract.sh --write
set -u
cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit 1

Expand Down Expand Up @@ -83,6 +90,70 @@ command -v jq >/dev/null 2>&1 || { echo "FAIL jq is required to validate $INV"
[ -f "$INV" ] || { echo "FAIL $INV not found" >&2; exit 1; }
jq empty "$INV" >/dev/null 2>&1 || { echo "FAIL $INV does not parse as JSON" >&2; exit 1; }

# --- --write / --repoint ---------------------------------------------------------------------
# The two-step dance every stale-inventory failure below actually asks for: recompute
# payload_digest with the one blessed implementation (payload_digest_compute(), never a hand-typed
# recipe), set derived_at.head to the commit that recipe just ran against, write both fields back
# with jq so key order and indent survive untouched, then re-run the validation this same script
# does on every other invocation so `--write` cannot itself ship a stale file.
#
# Refuses on a dirty payload tree rather than repointing at a HEAD the digest was not actually
# computed from: derived_at.head names the commit payload_digest describes, and a payload file
# with uncommitted changes means HEAD is not that commit yet -- committing first is the only way
# to make the claim true, not something this flag can paper over.
# `needs_repoint` mirrors the contract's own definition of stale for these two fields ONLY well
# enough to answer "is there anything to fix" -- it is not a second implementation of the digest
# (payload_digest_compute() is still the only place that runs) and it does not replace the real
# checks below, which still run afterward and print the authoritative pass/fail lines. Without
# this, --write would rewrite derived_at.head to current HEAD on every call, even one made right
# after an unrelated non-payload commit where the file already validates clean -- which is not a
# fix, it is churn, and it breaks the "second --write on a clean tree changes nothing" contract.
needs_repoint(){
[ "$(payload_digest_compute)" != "$(jq -r '.derived_at.payload_digest' "$INV")" ] && return 0
h=$(jq -r '.derived_at.head // empty' "$INV")
[ -z "$h" ] && return 0
printf '%s' "$h" | grep -qE '^[0-9a-f]{40}$' || return 0
# Absent from a shallow checkout: the real check below SKIPs rather than fails this case, so
# --write must not force a rewrite it cannot justify either.
git cat-file -e "$h^{commit}" 2>/dev/null || return 1
git merge-base --is-ancestor "$h" HEAD 2>/dev/null || return 0
# shellcheck disable=SC2086 # PAYLOAD_PATHS is a deliberate word list of pathspecs, same as payload_digest_compute() above
[ -n "$(git diff --name-only "$h" HEAD -- $PAYLOAD_PATHS 2>/dev/null)" ] && return 0
return 1
}

WRITE_MODE=0
WROTE=0
if [ "${1:-}" = "--write" ] || [ "${1:-}" = "--repoint" ]; then
WRITE_MODE=1
# shellcheck disable=SC2086 # PAYLOAD_PATHS is a deliberate word list of pathspecs, same as payload_digest_compute() above
dirty=$(git status --porcelain -- $PAYLOAD_PATHS)
if [ -n "$dirty" ]; then
echo "FAIL inventory-contract --write: the payload has uncommitted changes:" >&2
printf '%s\n' "$dirty" >&2
echo "commit the payload first, the head must name a commit" >&2
exit 2
fi
if needs_repoint; then
write_head=$(git rev-parse HEAD)
write_digest=$(payload_digest_compute)
write_tmp=$(mktemp "${INV}.XXXXXX") || { echo "FAIL inventory-contract --write: mktemp failed" >&2; exit 1; }
if ! jq --arg h "$write_head" --arg d "$write_digest" \
'.derived_at.head = $h | .derived_at.payload_digest = $d' \
"$INV" > "$write_tmp"; then
echo "FAIL inventory-contract --write: jq failed to write $INV" >&2
rm -f "$write_tmp"
exit 1
fi
mv "$write_tmp" "$INV"
WROTE=1
echo "inventory-contract --write: derived_at.head -> $write_head, derived_at.payload_digest -> $write_digest"
else
echo "inventory-contract --write: derived_at.head and derived_at.payload_digest already satisfy the contract; nothing to do"
fi
echo
fi

# --- contract_version: the hand-bumped SCHEMA version. An unknown value must fail loudly -- a
# validator that silently accepts a schema it does not understand is worse than no validator,
# because everything below reads fields whose meaning may have changed out from under it.
Expand Down Expand Up @@ -116,7 +187,7 @@ stated_digest=$(jq -r '.derived_at.payload_digest' "$INV")
if [ "$computed_digest" = "$stated_digest" ]; then
pass "payload_digest matches the tree ($computed_digest)"
else
fail "payload_digest" "claude/inventory.json's derived_at.payload_digest is $stated_digest; the tree's is $computed_digest. The snapshot is stale."
fail "payload_digest" "claude/inventory.json's derived_at.payload_digest is $stated_digest; the tree's is $computed_digest. The snapshot is stale. run: tests/inventory-contract.sh --write"
fi

# The file no longer carries an executable recipe, so nothing here can be tricked into running
Expand Down Expand Up @@ -172,7 +243,7 @@ elif git merge-base --is-ancestor "$stated_head" HEAD 2>/dev/null; then
# thing that is actually checkable, and which is false in exactly the case above.
_hd_moved=$(git diff --name-only "$stated_head" HEAD -- $PAYLOAD_PATHS 2>/dev/null)
if [ -n "$_hd_moved" ]; then
fail "derived_at.head" "derived_at.head is $(printf '%.12s' "$stated_head") and is an ancestor of HEAD, but payload has changed since it: $(printf '%s' "$_hd_moved" | tr '\n' ' ') -- so payload_digest was not computed from that commit, whatever this field says. Re-point head at the commit whose payload the digest describes"
fail "derived_at.head" "derived_at.head is $(printf '%.12s' "$stated_head") and is an ancestor of HEAD, but payload has changed since it: $(printf '%s' "$_hd_moved" | tr '\n' ' ') -- so payload_digest was not computed from that commit, whatever this field says. Re-point head at the commit whose payload the digest describes. run: tests/inventory-contract.sh --write"
else
pass "derived_at.head is an ancestor of HEAD with no payload change since ($(printf '%.12s' "$stated_head"), $(git rev-list --count "$stated_head..HEAD" 2>/dev/null) commit(s) ago)"
fi
Expand Down Expand Up @@ -308,8 +379,15 @@ if [ "$FAIL" -eq 0 ]; then
else
echo "inventory-contract: $RAN checks, all clean"
fi
if [ "$WRITE_MODE" -eq 1 ] && [ "$WROTE" -eq 1 ]; then
echo
echo "commit: git commit -am \"Re-point inventory at $(git rev-parse --short HEAD)\""
fi
exit 0
else
echo "inventory-contract: $RAN checks, $SKIPPED skipped, FAILURES ABOVE"
if [ "$WRITE_MODE" -eq 1 ] && [ "$WROTE" -eq 1 ]; then
echo "--write updated derived_at.head/payload_digest but validation still fails on the above -- not printing a commit command over a file that is not clean" >&2
fi
exit 1
fi
Loading