docs: the withdrawal fix shipped in v1.9.1 unnamed — say so, and re-pin the canary
v1.9.1 bakes mempalace-toolkit e68ee20, which contains e2b060a. Requester-side
ask withdrawal has therefore been LIVE on every v1.9.1 device since 2026-09-10,
while the v1.9.0 section of CHANGELOG.md still read "not yet pinned ... this
image still pins e45f6b4", and the mempalace skill still told every agent at
session start that a withdrawal is impossible.
Measured, two independent routes, expectation recorded before looking:
- published image label, :v1.9.1-studio and :latest-studio (same digest):
se.jordbo.pi-devbox.mempalace-toolkit-ref = e68ee2071ca3ad39...
- ancestry: e2b060a is an ancestor of e68ee20
- baked mempalace.ts sha 7c16fe14 != v1.8.14's dfca71e9 (different bytes)
- grep -c isWithdrawn on this container's baked copy = 0 (v1.8.14, so
tor-ms22 cannot exercise the behaviour it is documenting)
The first label read came back EMPTY, and that empty was a claim about the
request rather than the image: Hub redirects blob fetches to a CDN and curl
without -L returns 0 bytes at exit 0. Recorded in the notes, because a registry
audit reporting "no labels" is missing -L until proven otherwise.
Changes:
- CHANGELOG Unreleased: the floating-ref mechanism, which is the reusable
part. ARG MEMPALACE_TOOLKIT_REF=main + CI resolving it to a SHA at build
time means a release absorbs whatever toolkit main holds, and "what
behaviour did this image gain" is a question nobody is forced to answer.
The fleet rule (name the pickup before tagging) was honoured for the
feed-tick commits v1.9.1 names, and missed for one in the same range.
- CHANGELOG v1.9.0: the stale caveat is ANNOTATED, not rewritten. The
sentence is the evidence for how the drift happened; deleting it would
destroy the only trace. Same discipline as fleet-ops d42412c.
- CHANGELOG Unreleased: corrected the size entry's "needs no base rebuild"
claim. True of that entry alone, false of the release now that the
vendored skill snapshot -- a base_tag input -- moves with it (~67 min).
- rootfs mempalace skill snapshot 44472 -> 46045 B, refreshed via
scripts/vendor-mempalace-skill.sh so the bytes and SKILLSET_SNAPSHOT_REF
(4d7c0ea -> e9e45f7) move together; --check confirms exact match.
- scripts/smoke-test.sh canary RE-PINNED. The retired pair was still green
against the new snapshot, i.e. blind to this refresh for the same reason
the pre-v1.8.13 pair was blind to that one. The replacement is stronger
than any predecessor here because BOTH witnesses come from the same
upstream commit: e9e45f7 added "Withdrawing an ask you sent" and deleted
"nothing anyone can do about it from the other end", the sentence the new
bullet contradicts. Directions measured against both files, then the
canary body EXECUTED against each: new -> rc=0 "ok", old -> rc=1 empty.
A canary whose negative witness was removed by the commit it pins fails
loudly on stale bytes instead of merely failing to notice them.
Gates: check-doc-drift OK, check-skill-floor OK, vendor --check OK,
hooks/pre-push OK (16 shell files clean at severity error).
Not fixable here: isWithdrawn is deployed and UNPROVEN. 17 assertions and 4
mutation kills, never once exercised on a released image against the live
logstream. Ask routed to a v1.9.1 device.
This commit is contained in:
@@ -100,6 +100,77 @@ Touches `Dockerfile.variant` and `scripts/smoke-test.sh` only: `Dockerfile.base`
|
|||||||
is unchanged, so this needs no base rebuild and should **ride the next release**
|
is unchanged, so this needs no base rebuild and should **ride the next release**
|
||||||
rather than burn a cycle of its own.
|
rather than burn a cycle of its own.
|
||||||
|
|
||||||
|
> **Superseded by the entry below:** that entry refreshes the vendored mempalace
|
||||||
|
> skill snapshot, which **is** hashed into `base_tag`. The release as a whole now
|
||||||
|
> costs a base rebuild (~67 min). The *size* work above still needs none of its
|
||||||
|
> own; the two simply travel together now.
|
||||||
|
|
||||||
|
**A behaviour change reached the fleet without any release naming it, and a
|
||||||
|
paragraph in these notes kept saying it had not.** v1.9.1 bakes
|
||||||
|
`mempalace-toolkit` **`e68ee20`**, which contains **`e2b060a`** — requester-side
|
||||||
|
ask withdrawal (`isWithdrawn`, RFC 003 §3.3 clause 4). So the behaviour has been
|
||||||
|
live on every v1.9.1 device since 2026-09-10, while the v1.9.0 section of this
|
||||||
|
file still read "not yet pinned … this image still pins `e45f6b4`" and the
|
||||||
|
mempalace skill still told every agent, at session start, that a withdrawal is
|
||||||
|
impossible: *"there is nothing anyone can do about it from the other end."*
|
||||||
|
|
||||||
|
The mechanism is the point, because it will do this again. `Dockerfile.variant`
|
||||||
|
carries `ARG MEMPALACE_TOOLKIT_REF=main` and `docker-publish.yml` resolves it to
|
||||||
|
a commit SHA at build time (`gitea_sha mempalace-toolkit`). A release therefore
|
||||||
|
absorbs *whatever toolkit `main` holds at that moment*, and "what behaviour did
|
||||||
|
this image gain?" is a question **nobody is structurally forced to answer**. This
|
||||||
|
fleet already has the rule — a floating ref that pulls a behaviour change into
|
||||||
|
the image must be named in the CHANGELOG *before* tagging. It was honoured for
|
||||||
|
the feed-tick fix, which v1.9.1 names explicitly (`309980b`, `e68ee20`), and
|
||||||
|
missed for the commit sitting in the same range.
|
||||||
|
|
||||||
|
Measured before being written, two independent routes, expectation recorded
|
||||||
|
first ("label should read ≥ `e68ee20`, since the build at 22:00Z postdates that
|
||||||
|
commit's 18:58Z"):
|
||||||
|
|
||||||
|
| route | result |
|
||||||
|
|---|---|
|
||||||
|
| Docker Hub config-blob label, `:v1.9.1-studio` and `:latest-studio` (same digest) | `se.jordbo.pi-devbox.mempalace-toolkit-ref = e68ee2071ca3ad39…` |
|
||||||
|
| `git merge-base --is-ancestor e2b060a e68ee20` | ancestor — the fix is inside the baked ref |
|
||||||
|
| baked `extensions/pi/mempalace.ts` sha256 vs v1.8.14's | `7c16fe14…` vs `dfca71e9…` — different bytes, so not the pre-fix file |
|
||||||
|
| `grep -c isWithdrawn` on this v1.8.14 container's baked copy | `0` — confirms the split, and that tor-ms22 cannot exercise it |
|
||||||
|
|
||||||
|
The first attempt at that label read **empty**, and the empty result was a claim
|
||||||
|
about the request, not the image: Docker Hub redirects blob fetches to a CDN and
|
||||||
|
`curl` without `-L` returns 0 bytes with exit 0. A registry audit that reports
|
||||||
|
"no labels" should be assumed to be missing `-L` until proven otherwise.
|
||||||
|
|
||||||
|
So the skill is updated rather than deferred (skillset **`e9e45f7`**, vendored
|
||||||
|
here with `scripts/vendor-mempalace-skill.sh`, `44472 → 46045 B`). Two things
|
||||||
|
were deliberate:
|
||||||
|
|
||||||
|
- **The "silence is not an answer" rule keeps its teeth.** An ask still stays
|
||||||
|
owed until the *recipient's* terminal event; what is new is a release by the
|
||||||
|
**asker**, explicitly marked. Stated that way round on purpose — the wrong
|
||||||
|
reading of this change is "withdrawals happen, so I need not reply".
|
||||||
|
- **The precondition ships with the rule**, because this skill is read on images
|
||||||
|
that lack the behaviour (tor-ms22, right now):
|
||||||
|
`grep -c isWithdrawn /opt/mempalace-toolkit/extensions/pi/mempalace.ts`, where
|
||||||
|
`0` means the withdrawal will not reach the recipient's mailbox. Same shape as
|
||||||
|
the provenance bullet's live-bridge check.
|
||||||
|
|
||||||
|
**The snapshot canary is re-pinned, and this time it fails on the old bytes
|
||||||
|
instead of merely failing to notice them.** The retired pair (`"Diaries
|
||||||
|
self-heal…"` present / `"Agent diaries live in"` absent) was still green against
|
||||||
|
the new snapshot, so it was blind to this refresh exactly as the pre-v1.8.13 pair
|
||||||
|
was blind to that one. The replacement is stronger than any predecessor here
|
||||||
|
because **both witnesses come from the same upstream commit**: `e9e45f7` added
|
||||||
|
`"Withdrawing an ask you sent"` and deleted `"nothing anyone can do about it from
|
||||||
|
the other end"`, the sentence the new bullet contradicts. Directions were
|
||||||
|
measured against both files rather than read off the diff (`new=1/old=0` and
|
||||||
|
`new=0/old=1`), then the canary body was **executed** against each: new → `rc=0
|
||||||
|
ok`, old → `rc=1` empty.
|
||||||
|
|
||||||
|
Still outstanding, and not fixable from this device: `isWithdrawn` has 17
|
||||||
|
assertions and 4 independent mutation kills, but has **never been exercised on a
|
||||||
|
released image against the live logstream**. It is deployed and unproven — a
|
||||||
|
different state from untested, and a worse one to leave unlabelled.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v1.9.1 — 2026-09-10
|
## v1.9.1 — 2026-09-10
|
||||||
@@ -343,6 +414,20 @@ derivation's `mine` query at the newest end (`order: "desc"`); with the previous
|
|||||||
default `asc` + `limit: 100`, a device passing 100 authored events would have its
|
default `asc` + `limit: 100`, a device passing 100 authored events would have its
|
||||||
recent replies fall out of the join window and see answered asks resurface.
|
recent replies fall out of the join window and see answered asks resurface.
|
||||||
|
|
||||||
|
> **Corrected 2026-09-14 (`pi@tor-ms22`, the device in the measured cost above).**
|
||||||
|
> "This image still pins `e45f6b4`" and "until an image bakes `e2b060a` or later"
|
||||||
|
> were true when written on 2026-09-09 and are **false for the running fleet**.
|
||||||
|
> **v1.9.1 bakes `e68ee20`**, a descendant of `e2b060a`, so requester-side
|
||||||
|
> withdrawal is LIVE wherever v1.9.1 runs. Measured from the published image's own
|
||||||
|
> label (`se.jordbo.pi-devbox.mempalace-toolkit-ref`) rather than from these
|
||||||
|
> notes, and cross-checked by ancestry and by the baked `mempalace.ts` sha
|
||||||
|
> differing from v1.8.14's. Nothing here was mis-stated on purpose:
|
||||||
|
> `ARG MEMPALACE_TOOLKIT_REF=main` is resolved to a commit SHA by CI at build
|
||||||
|
> time, so the release absorbed the commit without anybody having to name it,
|
||||||
|
> while this paragraph went on asserting it had not. Left standing rather than
|
||||||
|
> rewritten — the sentence is the evidence for how the drift happened. See
|
||||||
|
> Unreleased.
|
||||||
|
|
||||||
**Four small packages, each chosen from a gap that was measured rather than
|
**Four small packages, each chosen from a gap that was measured rather than
|
||||||
imagined.** All four were picked by looking back at a real session — the
|
imagined.** All four were picked by looking back at a real session — the
|
||||||
`gitea.egl.lan`/FreeIPA debugging of 2026-09-09..10 — and asking which absences
|
`gitea.egl.lan`/FreeIPA debugging of 2026-09-09..10 — and asking which absences
|
||||||
|
|||||||
+1
-1
@@ -481,7 +481,7 @@ ARG MEMPALACE_TOOLKIT_REF=main
|
|||||||
# no ~67-minute base rebuild. (scripts/check-base-hash.sh scans only
|
# no ~67-minute base rebuild. (scripts/check-base-hash.sh scans only
|
||||||
# Dockerfile.base, so no folding into the base hash is required — nor would
|
# Dockerfile.base, so no folding into the base hash is required — nor would
|
||||||
# it be correct, since this ARG changes nothing about the base's contents.)
|
# it be correct, since this ARG changes nothing about the base's contents.)
|
||||||
ARG SKILLSET_SNAPSHOT_REF=4d7c0ea9caeb3a1d6d9b04cf34f3fca5f9df4985
|
ARG SKILLSET_SNAPSHOT_REF=e9e45f7acdde490c3b5d24ce5f508bff8785c2c7
|
||||||
|
|
||||||
# Dockerfile.base sets description="pi-devbox — base image (variant-independent)"
|
# Dockerfile.base sets description="pi-devbox — base image (variant-independent)"
|
||||||
# and every variant INHERITS it, so both published images used to advertise
|
# and every variant INHERITS it, so both published images used to advertise
|
||||||
|
|||||||
@@ -535,7 +535,10 @@ Two consequences worth internalising:
|
|||||||
- **"Seen, not doing it" is a legitimate ack** — `status="blocked"` or
|
- **"Seen, not doing it" is a legitimate ack** — `status="blocked"` or
|
||||||
`"superseded"` plus the reason. Silence is not, and it is not merely rude:
|
`"superseded"` plus the reason. Silence is not, and it is not merely rude:
|
||||||
with no terminal event of yours to join to, the ask stays in the owed set
|
with no terminal event of yours to join to, the ask stays in the owed set
|
||||||
indefinitely and there is nothing anyone can do about it from the other end.
|
indefinitely. The *original requester* — and nobody else — can release it from
|
||||||
|
the other end, but only by saying so explicitly: see **Withdrawing an ask you
|
||||||
|
sent** below. That is a release by the asker, not an escape for the answerer.
|
||||||
|
While the ask still stands, only *your* terminal event clears it.
|
||||||
- **Nothing expires, and it should not.** An `open` with no terminal reply is
|
- **Nothing expires, and it should not.** An `open` with no terminal reply is
|
||||||
still live by definition, and the finished threads are valuable history. If
|
still live by definition, and the finished threads are valuable history. If
|
||||||
content is genuinely perishable ("do not push to main for the next hour"), say
|
content is genuinely perishable ("do not push to main for the next hour"), say
|
||||||
@@ -570,6 +573,24 @@ Two consequences worth internalising:
|
|||||||
be matched to it at all.
|
be matched to it at all.
|
||||||
- **Corrections are new events, never edits.** Say explicitly what you retract
|
- **Corrections are new events, never edits.** Say explicitly what you retract
|
||||||
and name the id — drawer or event — that carried the withdrawn claim.
|
and name the id — drawer or event — that carried the withdrawn claim.
|
||||||
|
- **Withdrawing an ask you sent: state it, never imply it.** Your release only
|
||||||
|
counts when the terminal event (a) comes from the same `from_agent` that sent
|
||||||
|
the ask, (b) is directed at that recipient exactly — never `*`, so a broadcast
|
||||||
|
can neither oblige nor release, (c) carries a terminal status (`claimed` and
|
||||||
|
`ready` are not terminal and do not release anything), (d) is strictly after
|
||||||
|
the ask, (e) joins it via `ack_of` or the same `correlation_id`, **and (f)
|
||||||
|
names that ask in `metadata.withdraws` or `metadata.closes`.** Prose in the
|
||||||
|
body does not count, and neither does a bare terminal event on the
|
||||||
|
correlation: inferring release from *any* terminal would let your own
|
||||||
|
bookkeeping silently delete a real obligation, so the release must be stated.
|
||||||
|
Needs toolkit ≥ `e2b060a` (image ≥ `v1.9.1`) — check with
|
||||||
|
`grep -c isWithdrawn /opt/mempalace-toolkit/extensions/pi/mempalace.ts` and
|
||||||
|
read `0` as "my withdrawal will have no effect on their mailbox". Measured
|
||||||
|
cost of getting it wrong: a `v1.8.13` rollout ask was withdrawn by its sender,
|
||||||
|
who recorded it as done; the recipient's derivation never saw the release and
|
||||||
|
still reported the ask owed **41 hours later**, for a release that device
|
||||||
|
never installed — and the asymmetry was invisible from the sender's side
|
||||||
|
(RFC 003 §3.3 clause 4).
|
||||||
- **Put a retraction where the reader will look.** A *directed open ask* reaches a
|
- **Put a retraction where the reader will look.** A *directed open ask* reaches a
|
||||||
live agent's mailbox; a **terminal-status event reaches no mailbox at all**, and
|
live agent's mailbox; a **terminal-status event reaches no mailbox at all**, and
|
||||||
a *drawer* is what a future semantic search finds. If you filed advice as a
|
a *drawer* is what a future semantic search finds. If you filed advice as a
|
||||||
|
|||||||
+16
-1
@@ -697,7 +697,22 @@ exec_test "mempalace skill linked (fallback)" 'test -L $HOME/.agents/skills
|
|||||||
# (forgotten bump AND re-vendored stale snapshot). Upstream content behind this
|
# (forgotten bump AND re-vendored stale snapshot). Upstream content behind this
|
||||||
# refresh: the bare project-name wing convention and the <harness>@<device>
|
# refresh: the bare project-name wing convention and the <harness>@<device>
|
||||||
# added_by rule.
|
# added_by rule.
|
||||||
exec_test "mempalace skill snapshot is current" 'f=$HOME/.agents/skills/mempalace/SKILL.md; grep -q "Diaries self-heal; plain drawers do not" "$f" && ! grep -q "Agent diaries live in" "$f" && echo ok'
|
#
|
||||||
|
# Unreleased: RE-PINNED again on refresh e9e09d9 -> e9e45f7. The retired pair was
|
||||||
|
# still green against the new snapshot (the diaries section was untouched), so it
|
||||||
|
# was blind to this refresh for the same reason the v1.8.13 pair was blind to
|
||||||
|
# that one. The replacement pair is unusually strong because BOTH witnesses come
|
||||||
|
# out of the same upstream commit: skillset e9e45f7 ADDED the "Withdrawing an ask
|
||||||
|
# you sent" bullet and DELETED the sentence "there is nothing anyone can do about
|
||||||
|
# it from the other end" that the new bullet contradicts. Directions were
|
||||||
|
# MEASURED against both files, not read off the diff: "Withdrawing an ask you
|
||||||
|
# sent" is new=1/old=0, "nothing anyone can do about it from the other end" is
|
||||||
|
# new=0/old=1. A canary whose negative witness was removed by the very commit it
|
||||||
|
# pins fails loudly on the OLD bytes instead of merely failing to notice them,
|
||||||
|
# which is the property every previous pair here lacked. Upstream content:
|
||||||
|
# requester-side ask withdrawal became DEPLOYED behaviour once v1.9.1 baked
|
||||||
|
# mempalace-toolkit e68ee20 (>= e2b060a) through the floating MEMPALACE_TOOLKIT_REF.
|
||||||
|
exec_test "mempalace skill snapshot is current" 'f=$HOME/.agents/skills/mempalace/SKILL.md; grep -q "Withdrawing an ask you sent" "$f" && ! grep -q "nothing anyone can do about it from the other end" "$f" && echo ok'
|
||||||
# Link TARGETS, not just link existence: with no skillset mounted (as here) the
|
# Link TARGETS, not just link existence: with no skillset mounted (as here) the
|
||||||
# baked tree must be what resolves, for all four vendored skills.
|
# baked tree must be what resolves, for all four vendored skills.
|
||||||
exec_test "vendored skills resolve to the baked tree (no skillset mounted)" \
|
exec_test "vendored skills resolve to the baked tree (no skillset mounted)" \
|
||||||
|
|||||||
Reference in New Issue
Block a user