release: v1.8.9 — the version flag that blamed the wrong component
Lint / hadolint (push) Successful in 15s
Lint / actionlint (push) Successful in 18s
Publish Docker Image / resolve-versions (push) Successful in 9s
Publish Docker Image / base-decide (push) Successful in 9s
Publish Docker Image / build-base (push) Successful in 41m49s
Publish Docker Image / smoke (push) Successful in 4m51s
Publish Docker Image / smoke-studio (push) Successful in 4m59s
Publish Docker Image / build-variant-studio (push) Successful in 16m58s
Publish Docker Image / build-variant (push) Successful in 28m35s
Publish Docker Image / update-description (push) Successful in 7s
Publish Docker Image / promote-base-latest (push) Successful in 17s
Lint / hadolint (push) Successful in 15s
Lint / actionlint (push) Successful in 18s
Publish Docker Image / resolve-versions (push) Successful in 9s
Publish Docker Image / base-decide (push) Successful in 9s
Publish Docker Image / build-base (push) Successful in 41m49s
Publish Docker Image / smoke (push) Successful in 4m51s
Publish Docker Image / smoke-studio (push) Successful in 4m59s
Publish Docker Image / build-variant-studio (push) Successful in 16m58s
Publish Docker Image / build-variant (push) Successful in 28m35s
Publish Docker Image / update-description (push) Successful in 7s
Publish Docker Image / promote-base-latest (push) Successful in 17s
Two versions, two flags. `--expected-version` has only ever asserted
`pi --version`, but AGENTS.md step 4 spelled it `X.Y.Z` inside a checklist where
every other X.Y.Z is the pi-devbox tag. Run as documented for v1.8.8 the final
runtime gate of the release printed
✗ pi version mismatch: expected 1.8.8, got 0.84.3
and exited 1 — a red accusing the image of being the wrong version. Not one
reader's slip: the v1.8.8 release-readiness handoff from pi@emb-7kj4vr4g
propagated the same wrong spelling twice while correctly calling step 4 "not
ceremonial", so two independent readers converged on it. README.md had it right
all along, which means the two documents disagreed.
- new --expected-image-version asserts the pi-devbox release tag, read from
release_tag in /etc/pi-devbox/build-manifest.json (no checkout, no network);
leading `v` optional on either side
- both flags detect being handed the other one's value, and the test is exact
rather than heuristic: the value is compared against the other quantity the
image itself reports, so it can only fire on a real mix-up
- neither flag is required now. With none, live `pi --version` is asserted
against the manifest's pi_version — not a tautology, since a stale pi in the
~/.pi/npm-global volume can shadow the baked one, exactly as a stale
npm:pi-atelier can in packages[]
- the header note replaced was stale and load-bearing: it claimed pi is resolved
from 'latest' and cannot be self-derived, while Dockerfile.variant pins
ARG PI_VERSION=0.84.3 and docker-publish.yml reads that ARG as its source of
truth. The same withdrawn claim also sat in cli_utils' pi-devbox-sanity --help
- argument parsing: a missing value, or a value that is another flag, is a usage
error instead of silently consuming the next argument; --help works
All fourteen flag combinations exercised by execution, including the two
manifest-absent branches and the shadowing branch a healthy container cannot
reach — mutation-tested with a doctored manifest so each failure branch was
observed firing rather than assumed present.
CHANGELOG also names what no commit here causes: mempalace-toolkit main moved
e70bef2 -> 5b8d78f, so this tag ships the auto-delivered logstream mailbox
because base_tag folds the resolved toolkit SHA. It would have landed either
way; going unnamed is the 553d865 shape that already caused one cross-host
misattribution. Component audit found nothing else to bump — pi, mempalace,
pi-atelier all equal their upstream latest, and every other floating ref
resolves to the commit already baked.
This commit is contained in:
+141
@@ -11,6 +11,147 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
||||
|
||||
---
|
||||
|
||||
## v1.8.9 — 2026-08-26
|
||||
|
||||
The coordination log gets a reader, and the release checklist's last gate stops
|
||||
accusing the wrong component.
|
||||
|
||||
### The mailbox arrives — named here *because nothing in this repo caused it*
|
||||
|
||||
**`mempalace-toolkit` main moves `e70bef2` → `5b8d78f` (exactly one commit, 281
|
||||
insertions / 9 deletions across `extensions/pi/mempalace.ts` and
|
||||
`extensions/pi/README.md`), and that is what actually ships the auto-delivered
|
||||
logstream mailbox.** No pi-devbox commit implements it. `docker-publish.yml`
|
||||
resolves `MEMPALACE_TOOLKIT_REF=main` to a concrete SHA at build time and folds
|
||||
that SHA into `base_tag`, so the mailbox would have landed in the next tagged
|
||||
image **whether or not this section existed** — which is precisely why it exists.
|
||||
That is the same shipped-undocumented shape as `553d865` in v1.8.7, and that one
|
||||
caused a cross-host misattribution: an agent on another machine reasoned about
|
||||
which image contained which behaviour from a CHANGELOG that never mentioned it.
|
||||
The rule this release adopts: **if a floating ref will pull a behaviour change
|
||||
into the image, name it in the CHANGELOG before tagging, not after.**
|
||||
|
||||
What the mailbox does, from the shipped code rather than from the design
|
||||
discussion:
|
||||
|
||||
- **The bridge was write-only.** It stamped provenance on the way *out* and never
|
||||
read the log back, so a directed ask reached an agent only if that agent
|
||||
happened to run `mempalace_event_list` itself. The channel carried real
|
||||
cross-machine traffic from 2026-08-18 onward with **zero readers** — every
|
||||
delivery in that window happened because a human said "check your mailbox".
|
||||
- **Doubly gated, exactly like the provenance stamper:** inert unless *both*
|
||||
`MEMPALACE_PI_DEVICE` and `MEMPALACE_REMOTE_URL` are set. An unstamped client
|
||||
has no address to be reached at, so there is nothing for it to read.
|
||||
- **On by default, opt out with `MEMPALACE_MAILBOX=0`.** Deliberate: an opt-in
|
||||
fix for a nobody-remembers-to-do-it problem only relocates the forgetting.
|
||||
Tunables: `MEMPALACE_MAILBOX_POLL_MS` (min gap between mid-session polls,
|
||||
default 300000) and `MEMPALACE_MAILBOX_RESURFACE_MS` (re-announce a still-owed
|
||||
ask after, default 3600000).
|
||||
- **Owed-ness is derived, never read off `status`.** `event_ack` appends and never
|
||||
mutates, and `status` is written once, so a directed `open` keeps matching the
|
||||
mailbox query forever — answered or not. A candidate counts as answered only
|
||||
when one of this device's own events has a **higher `seq`**, joins via
|
||||
`metadata.ack_of` or a shared `correlation_id`, and carries a terminal status
|
||||
(`applied`, `superseded`, `failed`, `blocked`). `claimed` and `ready` are
|
||||
deliberately **not** terminal — that is how "taken, but not finished" keeps
|
||||
resurfacing.
|
||||
- **`*` broadcasts are excluded from the owed set.** `to_agent: <me>` also matches
|
||||
broadcasts per the tool contract, so without this a broadcast written with
|
||||
`status="open"` would make every machine believe it personally owed the same
|
||||
answer — and the code would contradict the skill that documents it.
|
||||
- **The dedup map is in memory on purpose.** A restart forgets, so an already-seen
|
||||
ask can resurface: visible noise a human corrects in one turn. The opposite
|
||||
failure — suppressing an unanswered ask — is silent and permanent. Do not
|
||||
"fix" the noise by persisting it.
|
||||
- **Delivery queues, it never interrupts.** A sections push at
|
||||
`before_agent_start` plus a second `agent_settled` handler behind the 300 s
|
||||
floor, using `steer` and *not* `triggerTurn`: `agent_settled` means idle, so
|
||||
nothing wakes a model on inbound fleet traffic.
|
||||
|
||||
Measured on v1.8.8 (which bakes `e70bef2`, i.e. no mailbox) immediately before
|
||||
this release: the wake-up mailbox query had to be run by hand, returned **3**
|
||||
directed asks with `status="open"`, and the derivation above resolved **all
|
||||
three** as already answered — the third independent confirmation that the raw
|
||||
`status` filter never shrinks, and the first taken on a fresh container with no
|
||||
memory of having answered them.
|
||||
|
||||
### `--expected-image-version`: two versions, two flags
|
||||
|
||||
**`scripts/recreate-sanity-check.sh --expected-version 1.8.8` reported
|
||||
`✗ pi version mismatch: expected 1.8.8, got 0.84.3` and exit 1** — a red on the
|
||||
final runtime gate of a release, accusing the image of being the wrong version,
|
||||
when the flag had only ever asserted `pi --version`. `AGENTS.md` step 4 spelled
|
||||
it `--expected-version X.Y.Z` inside a checklist where every *other* `X.Y.Z` is
|
||||
the pi-devbox tag; `README.md` got it right, so the two documents disagreed.
|
||||
|
||||
Not hypothetical, and not one reader's slip: the v1.8.8 release-readiness handoff
|
||||
from `pi@emb-7kj4vr4g` (`evt_20260826T134919_a614ecfc2d4f`) propagated
|
||||
`--expected-version 1.8.8` twice, in its body and in
|
||||
`metadata.cannot_check_here`, while correctly calling step 4 "the runtime peer of
|
||||
the smoke gate, so it is not ceremonial". Two independent readers, one on another
|
||||
machine, converged on the wrong meaning. Left alone it puts a spurious red on
|
||||
every release, and the intuitive remedy — re-pull, re-recreate — is pure waste.
|
||||
|
||||
- **New `--expected-image-version X.Y.Z`** asserts the pi-devbox release tag,
|
||||
read from `release_tag` in `/etc/pi-devbox/build-manifest.json` (the image's
|
||||
own build-time ground truth — no checkout, no network, no Docker socket). A
|
||||
leading `v` is optional on either side, so `1.8.9` and `v1.8.9` both work.
|
||||
- **Both flags now detect being handed the other one's value**, and the test is
|
||||
exact rather than heuristic: the value is compared against the *other*
|
||||
quantity this image actually reports, so it can only fire when the mix-up is
|
||||
real. `--expected-version 1.8.9` now says *"is the pi-devbox IMAGE version,
|
||||
not the pi version — use `--expected-image-version`"*, and the reverse mix-up
|
||||
is caught the same way.
|
||||
- **Neither flag is required any more.** With none, the live `pi --version` is
|
||||
asserted against `pi_version` in the build manifest. That is not a tautology:
|
||||
`pi` resolves through `PATH`, and a stale install in the `~/.pi/npm-global`
|
||||
volume can shadow the baked one — the same shadowing this script already
|
||||
guards against for `npm:pi-atelier` in `packages[]`. Verified by mutating the
|
||||
manifest to a different version, which made the new check fail as intended.
|
||||
- **The header note it replaced was stale and load-bearing.** It claimed pi "is
|
||||
resolved from `latest` at CI build time and is NOT pinned … cannot self-derive
|
||||
an expected version". `Dockerfile.variant` pins `ARG PI_VERSION=0.84.3`, and
|
||||
`docker-publish.yml` *reads that ARG* as its source of truth (refusing to
|
||||
build on a floating value, checking it is published on npm, warning when npm
|
||||
is ahead). The same withdrawn claim also sat in `cli_utils`'s
|
||||
`pi-devbox-sanity --help`, the third place this confusion lived; fixed there
|
||||
too, in that repo.
|
||||
- Argument parsing hardened while in there: a flag whose value is missing — or
|
||||
is another flag — is now a usage error (exit 2) instead of silently consuming
|
||||
the next argument, and `--help` works.
|
||||
|
||||
All fourteen flag combinations were exercised by execution, including the two
|
||||
manifest-absent branches and the shadowing branch, which a healthy container
|
||||
cannot reach naturally — mutation-tested with a doctored manifest path so that
|
||||
each failure branch was observed *firing* rather than assumed present.
|
||||
|
||||
### Component audit: no bumps, and that is the finding
|
||||
|
||||
Checked before tagging, since a base rebuild was already forced:
|
||||
|
||||
| Component | In v1.8.8 | Upstream now | Action |
|
||||
|---|---|---|---|
|
||||
| pi (npm) | `0.84.3` (pinned) | `0.84.3` is `latest` | none |
|
||||
| mempalace (PyPI) | `3.8.0` (pinned) | `3.8.0` | none |
|
||||
| pi-atelier | `v0.8.2` (pinned) | `v0.8.2` highest tag | none |
|
||||
| pi-toolkit, pi-extensions, pi-fork, pi-observational-memory, pi-studio | floating | **identical to baked** | none |
|
||||
| skillset snapshot | `6eb20af` | `6eb20af` | none |
|
||||
| **mempalace-toolkit** | `e70bef2` | **`5b8d78f`** | ships the mailbox |
|
||||
|
||||
So the whole ~67-minute base rebuild this tag pays for is attributable to the
|
||||
toolkit SHA alone — `base_tag` folds it, and it moved. Every other floating ref
|
||||
resolved to the commit already baked (verified with `git ls-remote` per repo, not
|
||||
by reading a cached clone).
|
||||
|
||||
One claim in this audit came from a fork that had fabricated its findings — six
|
||||
plausible-looking toolkit commits with five nonexistent SHAs, a pi `0.84.4` that
|
||||
npm has never published, a pi-studio commit `ls-remote` says does not exist, and
|
||||
a compatibility floor of `0.8.2` where the code says `0.7.1`. Every row above was
|
||||
therefore re-measured directly. Recorded because the failure mode is specific:
|
||||
none of it looked wrong, and `git cat-file -e` is what caught it.
|
||||
|
||||
---
|
||||
|
||||
## v1.8.8 — 2026-08-26
|
||||
|
||||
The vendored `mempalace` skill snapshot stops being anonymous, and the
|
||||
|
||||
Reference in New Issue
Block a user