Commit Graph

3 Commits

Author SHA1 Message Date
joakimp e8ddeaf89f skills: a gate that could pass without checking, and a mailbox that never empties
Lint / hadolint (push) Successful in 9s
Lint / actionlint (push) Successful in 1m39s
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.
2026-08-26 14:38:07 +02:00
joakimp 49a6534093 docs: write down the coordination channel the fleet already runs on
Lint / hadolint (push) Successful in 13s
Lint / actionlint (push) Successful in 15s
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
joakimp e070e0bcbf skills: record the vendored snapshot's provenance, and report which copy wins
Lint / actionlint (push) Successful in 16s
Lint / hadolint (push) Successful in 16s
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.
2026-08-26 10:30:27 +02:00