feat(manifest): record WHICH pi-extensions skill copy shipped
Closes the half deliberately left open by cac5e00's skill-floor gate, and the
more important half: "the floor is currently fresh" is a fact with a shelf
life, whereas "the image says which copy it got" keeps working.
The refresh in Dockerfile.variant is guarded by
`[ -f /opt/pi-extensions/skill/SKILL.md ]`, so a build whose clone predates the
co-located skill keeps the vendored floor and still succeeds GREEN, with nothing
in the manifest, labels or logs separating that from a normal build. Afterwards
the two are indistinguishable by inspection -- same path, same filenames, same
permissions -- which is exactly how the floor went unnoticed from 2026-07-30 to
2026-09-10.
build-manifest.json gains pi_extensions_skill_source and
pi_extensions_skill_tree_sha256, MEASURED rather than passed as build-args, per
the ground-truth rule the surrounding block already follows -- and necessarily
so, since the outcome depends on the clone's contents and no ARG could express
it. Three values, because two would force a lie: package (served bytes equal
the clone's skill/), vendored-floor (clone had no skill/ at this ref), and
divergent (both exist but differ -- e.g. the clone ships SKILL.md but not
evaluate-extension-usage.py, so the served directory is a genuine MIX). No OCI
label mirrors these deliberately: LABEL cannot take a RUN-computed value, and a
label fed from an ARG would be the claim-not-measurement being removed here.
Two smoke assertions make the record a gate: the source must be named and be
`package` -- vendored-floor FAILS rather than warns, since these images track
main where the package has shipped skill/ since fa04d20, so a fallback means
the clone did not resolve as intended -- and the tree hash is recomputed over
the served directory, because a recorded hash never recompared is a claim.
pi-devbox-version annotates the line too: "baked (package copy)" normally, or a
yellow "(FALLBACK: vendored floor)". Its existing section reports which copy is
READ at runtime; this is the one fact decided at BUILD time and unrecoverable
later. Old images degrade cleanly -- field absent, jq // empty yields nothing,
line prints plain "baked" as before (verified against this v1.8.14 manifest).
Tested by running the exact logic against this container's real layout, with
the expected value written down before each: package (served == clone),
vendored-floor (clone path absent), divergent (clone lacking the .py while the
served dir has it), and null (empty served dir) -- all four as predicted. The
five pi-devbox-version render branches likewise, including the absent-field
case. Emitted JSON validated with jq for both the populated and null forms.
Gates green: lint-shell.sh (15 files), hadolint 2.15.1, actionlint 1.7.12,
check-base-hash.sh, check-skill-floor.sh, vendor-mempalace-skill.sh --check.
This commit is contained in:
@@ -187,6 +187,44 @@ recorded origin was stale. Consequently no `rootfs/` bytes changed, the
|
||||
smoke-test phrase canary stays valid, and this ARG alone would not have forced a
|
||||
base rebuild — the node bump does that anyway.
|
||||
|
||||
**The silent-fallback hole is closed: the image now records WHICH `pi-extensions`
|
||||
skill copy it shipped.** This was the half deliberately left open by the
|
||||
`skill-floor` gate above, and it is the more important half, because "the floor is
|
||||
currently fresh" is a fact with a shelf life while "the image says which copy it
|
||||
got" keeps working. The refresh step in `Dockerfile.variant` is guarded by
|
||||
`if [ -f /opt/pi-extensions/skill/SKILL.md ]`, so a build whose clone predates the
|
||||
co-located skill kept the vendored floor and still succeeded **green**, with
|
||||
nothing in the manifest, the labels or the logs distinguishing that from a normal
|
||||
build. The two outcomes are indistinguishable by inspection afterwards — same
|
||||
path, same filenames, same permissions — which is exactly how the floor went
|
||||
unnoticed from 2026-07-30 to 2026-09-10.
|
||||
|
||||
`build-manifest.json` gains `pi_extensions_skill_source` and
|
||||
`pi_extensions_skill_tree_sha256`, both **measured rather than passed in as
|
||||
build-args**, per the ground-truth rule the rest of that block already follows —
|
||||
and necessarily so here, since the outcome depends on the clone's contents and no
|
||||
ARG could express it. Three values, because two would force a lie:
|
||||
`package` (served bytes equal the clone's `skill/`), `vendored-floor` (the clone
|
||||
had no `skill/` at this ref, so the fallback shipped), and `divergent` — both
|
||||
exist but differ, e.g. the clone ships `SKILL.md` but not
|
||||
`evaluate-extension-usage.py`, leaving the served directory a genuine **mix** of
|
||||
package and floor. No OCI label mirrors these, deliberately: `LABEL` cannot take a
|
||||
value computed in a `RUN`, and a label fed from an ARG would be precisely the
|
||||
claim-not-measurement this change exists to remove.
|
||||
|
||||
Two `scripts/smoke-test.sh` assertions turn the record into a gate: one that the
|
||||
source is named and is `package` — `vendored-floor` **fails** rather than warns,
|
||||
since these images track `main` where the package has co-located `skill/` since
|
||||
`fa04d20`, so a fallback means the clone did not resolve as intended — and one
|
||||
that recomputes the tree hash over the served directory, because a recorded hash
|
||||
that is never recompared is a claim rather than a measurement. `pi-devbox-version`
|
||||
also annotates the line: `pi-extensions baked (package copy)` on the normal path,
|
||||
and a yellow `(FALLBACK: vendored floor — clone had no skill/)` otherwise. Its
|
||||
existing skill section reports which copy is being **read** at runtime; this is
|
||||
the one fact that is decided at **build** time and cannot be recovered later.
|
||||
Older images degrade cleanly — the field is absent, `jq // empty` yields nothing,
|
||||
and the line prints plain `baked` exactly as before.
|
||||
|
||||
---
|
||||
|
||||
## v1.8.14 — 2026-09-08
|
||||
|
||||
Reference in New Issue
Block a user