ci: audit MEMPALACE_VERSION the way PI_VERSION is audited
Closes the item v1.8.6 (and v1.8.5 before it) listed as "Still open": the palace pin was a literal string in Dockerfile.base with zero references in docker-publish.yml, while PI_VERSION had a concreteness gate, a published-on-registry check and a never-silently-adopt drift warning. resolve-versions now applies all of those to MEMPALACE_VERSION, read from Dockerfile.base so a local `docker build` and CI install the same version by construction, plus one gate pi does not need: a YANKED release is refused, because an exact pin installs one silently under PEP 592 and would have shipped a withdrawn palace client to the whole fleet. smoke gains `installed mempalace matches CI's audited pin` via a new EXPECTED_MEMPALACE_VERSION threaded into both smoke jobs. It is not redundant with `manifest mempalace_version matches the installed core`: that compares two properties of one image and cannot notice that both are the wrong version. The case this covers is a variant built FROM a cached base carrying an older pin — internally consistent, silently stale. Mutation-tested by extracting the shipped block out of the YAML and stubbing curl: 9 cases covering every gate, then once end-to-end against live PyPI. That found a real defect in the first draft — the yank message inlined a jq program inside $(...) inside a double-quoted string, where the escaping broke the filter (jq compile error) while the surrounding `exit 1` still fired: a gate that looked correct and reported garbage. Note: correcting Dockerfile.base's now-false "known gap, carried forward" comment forces a base rebuild (~67 min) on the next tag. Leaving a comment asserting the audit does not exist was the worse option.
This commit is contained in:
@@ -13,6 +13,57 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- **`MEMPALACE_VERSION` now gets the same CI audit as `PI_VERSION`** — closing
|
||||
the item v1.8.6 (and v1.8.5 before it) listed as "Still open". The pin was a
|
||||
literal string in `Dockerfile.base` with **zero** references anywhere in
|
||||
`.gitea/workflows/docker-publish.yml`, while `PI_VERSION` had ~20: a
|
||||
concreteness gate, a published-on-registry check, and a never-silently-adopt
|
||||
drift warning. `resolve-versions` now applies all of them to the palace pin,
|
||||
read from `Dockerfile.base` (not duplicated in the workflow, so a local
|
||||
`docker build` and CI install the same version by construction):
|
||||
|
||||
| Gate | Behaviour |
|
||||
|---|---|
|
||||
| not a concrete `X.Y.Z` | **error** — no floating palace version, same policy as pi |
|
||||
| not published on PyPI | **error** at resolve time, instead of a `uv tool install` failure mid-build |
|
||||
| **yanked** on PyPI | **error** — an exact pin installs a yanked release silently under PEP 592, so `mempalace==X` would have shipped a withdrawn client to the whole fleet |
|
||||
| newer release exists | **warning** naming what to audit before adopting (MCP tool-schema = the agent-facing contract; client/server skew against the central palace) |
|
||||
|
||||
Plus one smoke assertion, `installed mempalace matches CI's audited pin`,
|
||||
gated on a new `EXPECTED_MEMPALACE_VERSION` env threaded into both the `smoke`
|
||||
and `smoke-studio` jobs. It is **not** redundant with the existing `manifest
|
||||
mempalace_version matches the installed core`: that one compares two
|
||||
properties of a single image and therefore cannot notice that *both* are the
|
||||
wrong version. The failure mode this one covers is a variant built `FROM` a
|
||||
cached base whose `MEMPALACE_VERSION` pin was older — internally consistent,
|
||||
silently stale, invisible to every other assertion (the risk
|
||||
`scripts/check-base-hash.sh` exists to reduce but cannot eliminate).
|
||||
|
||||
**Mutation-tested rather than reasoned about**, by extracting the shipped
|
||||
block out of the YAML and running it with a stubbed `curl`: 9 cases —
|
||||
`latest` / `3.8` / absent ARG refused; 404 and a registry echoing a different
|
||||
version refused; a yanked release refused *with its reason*; a newer release
|
||||
warning without failing; a transient PyPI outage not failing a build whose pin
|
||||
is already verified; happy path silent and emitting the job output. Then once
|
||||
more end-to-end against live PyPI with the real `Dockerfile.base`. **This
|
||||
found a genuine defect in the first draft**: the yank message inlined a jq
|
||||
program inside a `$(...)` inside a double-quoted string, where the escaping
|
||||
broke the *filter* (jq compile error) while the surrounding `exit 1` still
|
||||
fired — a gate that looked correct and reported garbage. The reason is now
|
||||
hoisted into its own variable. The new smoke assertion was checked the same
|
||||
way, through the real `run` helper's `sh -c` quoting path: passes on `3.8.0`,
|
||||
fails on `3.7.1` *and* on `3.8.01` (exact equality, not the substring match
|
||||
the pi assertion uses), and skips cleanly when the env is unset so a local
|
||||
`smoke-test.sh` run is unaffected.
|
||||
|
||||
⚠️ **Costs a base rebuild on the next tag**: `Dockerfile.base` is hashed
|
||||
wholesale into `base_tag`, and its now-false "Known gap, carried forward"
|
||||
comment had to be corrected in place (leaving a comment that says the audit
|
||||
does not exist would repeat the shipped-false-claim mistake corrected below).
|
||||
Expect ~67 min, as for v1.8.5/v1.8.6.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Four build-provenance smoke assertions verified the presence of a manifest
|
||||
|
||||
Reference in New Issue
Block a user