Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34cf1e3810 |
+56
-19
@@ -11,7 +11,7 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Unreleased
|
## v1.8.8 — 2026-08-26
|
||||||
|
|
||||||
The vendored `mempalace` skill snapshot stops being anonymous, and the
|
The vendored `mempalace` skill snapshot stops being anonymous, and the
|
||||||
container starts saying which copy of each skill it is actually reading.
|
container starts saying which copy of each skill it is actually reading.
|
||||||
@@ -45,6 +45,23 @@ being fixed.**
|
|||||||
only, `2` cannot determine (ref absent from this clone). `AGENTS.md` step 2
|
only, `2` cannot determine (ref absent from this clone). `AGENTS.md` step 2
|
||||||
rewritten to state all three, since its promise that "the message
|
rewritten to state all three, since its promise that "the message
|
||||||
distinguishes the two" was exactly what the branch was breaking.
|
distinguishes the two" was exactly what the branch was breaking.
|
||||||
|
- **The staleness `NOTICE` then asserted a direction it had never tested** — the
|
||||||
|
same defect one layer down, found by pi@emb-7kj4vr4g against the real state of
|
||||||
|
its own host. The branch fired the notice on "recorded ≠ HEAD" and announced
|
||||||
|
that HEAD was the newer side, so a clone that was merely *behind* was told
|
||||||
|
*"has moved to 82a8d3c; the snapshot describes the older 5fd0d5c"* when
|
||||||
|
`82a8d3c` is `5fd0d5c`'s **ancestor**. Harmless to the verdict (`rc` stayed 0,
|
||||||
|
nothing was mis-verified) but it points the operator at a refresh — a
|
||||||
|
~67-minute base rebuild — when the real remedy is `git pull`. It now tests
|
||||||
|
ancestry with the `merge-base --is-ancestor` primitive the refresh path two
|
||||||
|
sections above already used, and reports three distinct verdicts: **stale**
|
||||||
|
(recorded is an ancestor — refresh), **your clone is behind** (HEAD is an
|
||||||
|
ancestor — pull, do not refresh), **diverged** (neither). All three verified by
|
||||||
|
execution; only the first was right before.
|
||||||
|
- **`--help` died with `unknown option: --help`.** The strict argument loop that
|
||||||
|
closed the silent-ignore hole never added a `--help` case, so the one script
|
||||||
|
whose argument *order* was itself a landmine had an erroring discoverability
|
||||||
|
path. It now prints its own header block.
|
||||||
- **`VENDORED.md` contradicted itself, in the release whose stated invariant is
|
- **`VENDORED.md` contradicted itself, in the release whose stated invariant is
|
||||||
non-contradiction.** Its hand-maintained "Snapshot provenance at last refresh"
|
non-contradiction.** Its hand-maintained "Snapshot provenance at last refresh"
|
||||||
line named skillset `670f7f1` — seven commits behind the ARG, and *the very
|
line named skillset `670f7f1` — seven commits behind the ARG, and *the very
|
||||||
@@ -88,8 +105,22 @@ earlier. Fixed in skillset `5fd0d5c`, which derives owed-ness by joining on
|
|||||||
terminal reply suppresses every later ask on the same thread forever. Because
|
terminal reply suppresses every later ask on the same thread forever. Because
|
||||||
the skillset is mounted live on every enrolled host, that correction was already
|
the skillset is mounted live on every enrolled host, that correction was already
|
||||||
deployed fleet-wide before this image was built; the vendored snapshot is
|
deployed fleet-wide before this image was built; the vendored snapshot is
|
||||||
resynced to it (`c04cd15` → `5fd0d5c`) so the no-clone fallback does not ship
|
resynced to it (`c04cd15` → `5fd0d5c` → `6eb20af`) so the no-clone fallback does
|
||||||
the withdrawn rule. Canary re-verified bidirectionally against the new bytes.
|
not ship the withdrawn rule. Canary re-verified bidirectionally against the new
|
||||||
|
bytes.
|
||||||
|
|
||||||
|
`6eb20af` adds the limit of that ordering test, found when pi@emb-7kj4vr4g
|
||||||
|
verified it rather than adopting it: **`seq` is replica-local.** It equals
|
||||||
|
`origin_seq` today only because one replica authors events for all four machines,
|
||||||
|
so a second replica could order the same pair differently and derive a different
|
||||||
|
owed-set from the same log — use `hlc` (already on every event, total and
|
||||||
|
causally consistent) once `mesh_peers` reports any peer. Documented as reasoning,
|
||||||
|
not measurement, since a second replica cannot be stood up to test it. The part
|
||||||
|
worth keeping is the **asymmetry**: local-`seq` skew makes an answered item
|
||||||
|
*resurface* (noise, self-correcting, visible), while a timestamp comparison
|
||||||
|
*suppresses an unanswered ask forever* (silent, permanent) — so anyone tempted to
|
||||||
|
"fix" a resurfacing item with `created_at` would be trading the safe failure for
|
||||||
|
the dangerous one.
|
||||||
|
|
||||||
**Also carried, previously undocumented:** `dbb7879` resynced the vendored
|
**Also carried, previously undocumented:** `dbb7879` resynced the vendored
|
||||||
`mempalace` snapshot to skillset `c04cd15` ("the withdrawal only holds where the
|
`mempalace` snapshot to skillset `c04cd15` ("the withdrawal only holds where the
|
||||||
@@ -275,14 +306,17 @@ the skillset actually is — a maintainer's clone, or any running container.
|
|||||||
because when every host is a thin client of one palace the stamped agent name
|
because when every host is a thin client of one palace the stamped agent name
|
||||||
is the only thing distinguishing them. Set both or neither: a container
|
is the only thing distinguishing them. Set both or neither: a container
|
||||||
without the device var can read the log but is addressable by nobody.
|
without the device var can read the log but is addressable by nobody.
|
||||||
- the skillset's `mempalace` skill (`82a8d3c`, live on every host that mounts
|
- the skillset's `mempalace` skill (`6eb20af`, live on every host that mounts
|
||||||
the skillset, no rebuild needed) — the **norms**: a mailbox query at wake-up,
|
the skillset, no rebuild needed) — the **norms**: a mailbox query at wake-up,
|
||||||
and the sender-declared ack contract, where a *directed* event with
|
and the sender-declared ack contract, where a *directed* event with
|
||||||
`status="open"` is owed a reply and a `*` broadcast owes nothing. Measured
|
`status="open"` is owed a reply and a `*` broadcast owes nothing. The
|
||||||
while designing it: an unfiltered mailbox returned 5 events, 4 of them
|
`status` filter earns its place by dropping broadcast noise — measured, an
|
||||||
finished broadcasts from eight days earlier, where `status="open"` returned
|
unfiltered mailbox returned 5 events, 4 of them finished broadcasts from
|
||||||
exactly the 1 that needed an answer — an unfiltered mailbox trains you to
|
eight days earlier — but that is **all** it does; it does not compute
|
||||||
ignore it, so the filter is the feature.
|
owed-ness, and the version of this entry that claimed otherwise is withdrawn
|
||||||
|
above. Measured today, both machines: the raw filter returns 2 asks here and
|
||||||
|
1 there, **every one already answered**, while the derivation returns 0 for
|
||||||
|
both. Dropping noise and deciding what is owed are two different jobs.
|
||||||
- mempalace-toolkit `extensions/pi/README.md` (`e70bef2`) — the **mechanism**,
|
- mempalace-toolkit `extensions/pi/README.md` (`e70bef2`) — the **mechanism**,
|
||||||
including that the bridge is *write-only* today (it stamps events going out
|
including that the bridge is *write-only* today (it stamps events going out
|
||||||
and never reads the log, so nothing in this image polls on the agent's
|
and never reads the log, so nothing in this image polls on the agent's
|
||||||
@@ -290,16 +324,19 @@ the skillset actually is — a maintainer's clone, or any running container.
|
|||||||
implements `GET /logstream/stream`, but a reverse proxy exposing only `/mcp`
|
implements `GET /logstream/stream`, but a reverse proxy exposing only `/mcp`
|
||||||
makes it unreachable — verified by 404s against the real endpoint.
|
makes it unreachable — verified by 404s against the real endpoint.
|
||||||
|
|
||||||
⚠️ **This makes the vendored snapshot stale on purpose.** The skill edit is in
|
⚠️ **The snapshot was refreshed rather than left stale.** The skill edits landed
|
||||||
the skillset (`82a8d3c`), so `SKILLSET_SNAPSHOT_REF` still records `c04cd15`
|
in the skillset (`5fd0d5c`, then `6eb20af`), so `SKILLSET_SNAPSHOT_REF` was
|
||||||
and `scripts/vendor-mempalace-skill.sh --check` now exits 1 with
|
resynced to match and `scripts/vendor-mempalace-skill.sh --check` is a clean
|
||||||
*"has moved to 82a8d3c; the snapshot describes the older c04cd15"*. Refreshing
|
`OK` with no notice: the no-clone fallback carries the **corrected** protocol,
|
||||||
it is a deliberate release-day decision, not an oversight — hence the new
|
not the withdrawn one. That mattered more than currency usually does, because
|
||||||
step 2 in `AGENTS.md` § *Release-day checklist*, which states both that the
|
the superseded copy contained an instruction — the `mesh_peers` gate — that
|
||||||
refresh costs a base rebuild and that skipping it is legitimate because every
|
actively told a reader to skip the feature. Refreshing remains a deliberate
|
||||||
enrolled host reads its live clone. What is not legitimate is skipping it
|
release-day decision rather than an automatic one: it costs a base rebuild, and
|
||||||
*silently*, which is precisely what the new manifest fields and
|
skipping it is legitimate because every enrolled host reads its live clone.
|
||||||
`pi-devbox-version` output make impossible.
|
What is not legitimate is skipping it *silently*, which is what the new manifest
|
||||||
|
fields and `pi-devbox-version` output make impossible — hence step 2 in
|
||||||
|
`AGENTS.md` § *Release-day checklist*. In this release the refresh was free:
|
||||||
|
`rootfs/` was already changing, so the base rebuild was forced anyway.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -305,7 +305,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=5fd0d5c406df506fd0e70977b6bd87f5fbc3b803
|
ARG SKILLSET_SNAPSHOT_REF=6eb20af181f0147cb8c1377f6e36a6a47a68e8e5
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -462,6 +462,24 @@ every later ask on the same `correlation_id` for good; verified on a live thread
|
|||||||
where a `ready` reply at `seq` 16 sits *before* the request at `seq` 17 that it
|
where a `ready` reply at `seq` 16 sits *before* the request at `seq` 17 that it
|
||||||
obviously cannot have answered.
|
obviously cannot have answered.
|
||||||
|
|
||||||
|
**On a real mesh, compare `hlc` instead.** `seq` is *replica-local*: it equals
|
||||||
|
`origin_seq` today only because a single replica authors events for every
|
||||||
|
machine. Enrol a second replica and a late-syncing peer event gets a late local
|
||||||
|
`seq`, so two replicas can order the same pair differently and derive different
|
||||||
|
owed-sets from the same log. Every event already carries `hlc`
|
||||||
|
(`<millis>-<counter>-<replica_id>`), which is total and causally consistent.
|
||||||
|
So: compare `seq` while `mempalace_mesh_peers` reports no peers, `hlc` once it
|
||||||
|
reports any, and `created_at` never. (This is a legitimate use of `mesh_peers` —
|
||||||
|
choosing an ordering key — not the discredited gate on *whether* to read your
|
||||||
|
mailbox at all.)
|
||||||
|
|
||||||
|
**The failure directions are not symmetric, which is why this is safe to get
|
||||||
|
slightly wrong.** Local-`seq` skew can make an already-answered item *resurface*
|
||||||
|
as owed: noise, self-correcting, and visible. A timestamp comparison can
|
||||||
|
*suppress an unanswered ask forever*: silent and permanent. So if you ever see an
|
||||||
|
item you know you answered come back, do **not** "fix" it by reaching for
|
||||||
|
`created_at` — you would be trading the safe failure for the dangerous one.
|
||||||
|
|
||||||
This also supplies the "taken, not finished" state that looked missing:
|
This also supplies the "taken, not finished" state that looked missing:
|
||||||
`claimed` and `ready` are deliberately **not** terminal, so work you have picked
|
`claimed` and `ready` are deliberately **not** terminal, so work you have picked
|
||||||
up keeps resurfacing until you close it out. No extra convention, no new field.
|
up keeps resurfacing until you close it out. No extra convention, no new field.
|
||||||
|
|||||||
@@ -86,7 +86,13 @@ for arg in "$@"; do
|
|||||||
case "$arg" in
|
case "$arg" in
|
||||||
--check) MODE="check" ;;
|
--check) MODE="check" ;;
|
||||||
--force) FORCE=1 ;;
|
--force) FORCE=1 ;;
|
||||||
--*) die "unknown option: $arg" ;;
|
# This is the one script whose argument ORDER was itself a landmine, so the
|
||||||
|
# path that documents the trap must not be the path that errors.
|
||||||
|
-h|--help)
|
||||||
|
awk 'NR>1 && /^#/ { sub(/^# ?/, ""); print; next } NR>1 { exit }' "$0"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--*) die "unknown option: $arg (try --help)" ;;
|
||||||
*)
|
*)
|
||||||
[ -z "$ROOT" ] || die "unexpected extra argument: $arg (root already set to $ROOT)"
|
[ -z "$ROOT" ] || die "unexpected extra argument: $arg (root already set to $ROOT)"
|
||||||
ROOT="$arg"
|
ROOT="$arg"
|
||||||
@@ -110,6 +116,12 @@ fi
|
|||||||
[ -d "$ROOT/.git" ] || die "not a git clone: $ROOT"
|
[ -d "$ROOT/.git" ] || die "not a git clone: $ROOT"
|
||||||
[ -f "$ROOT/$REL_PATH" ] || die "no $REL_PATH in $ROOT"
|
[ -f "$ROOT/$REL_PATH" ] || die "no $REL_PATH in $ROOT"
|
||||||
[ -f "$VENDORED" ] || die "vendored snapshot missing: $VENDORED"
|
[ -f "$VENDORED" ] || die "vendored snapshot missing: $VENDORED"
|
||||||
|
# LOAD-BEARING, DO NOT DELETE AS "REDUNDANT WITH THE EXISTENCE PROBES": -f
|
||||||
|
# accepts an empty file, and sha256 of an empty file equals sha256 of a failed
|
||||||
|
# pipeline's empty stdin. Guarding it HERE, before mode dispatch, makes that
|
||||||
|
# collision unreachable by construction rather than by a probe further down --
|
||||||
|
# which also means no test below exercises the collision any more. Remove this
|
||||||
|
# line and the false "OK" for a nonexistent ref returns with nothing failing.
|
||||||
[ -s "$VENDORED" ] || die "vendored snapshot is empty: $VENDORED"
|
[ -s "$VENDORED" ] || die "vendored snapshot is empty: $VENDORED"
|
||||||
|
|
||||||
head_sha=$(git -C "$ROOT" rev-parse HEAD 2>/dev/null) || die "cannot read HEAD of $ROOT"
|
head_sha=$(git -C "$ROOT" rev-parse HEAD 2>/dev/null) || die "cannot read HEAD of $ROOT"
|
||||||
@@ -200,8 +212,20 @@ if [ "$MODE" = "check" ]; then
|
|||||||
# clean and the ref simply moved on — a message that names the wrong cause
|
# clean and the ref simply moved on — a message that names the wrong cause
|
||||||
# is the same defect class as a canary pinned to a deleted phrase.
|
# is the same defect class as a canary pinned to a deleted phrase.
|
||||||
if [ "$recorded" != "$head_sha" ] && [ "$blob_sha" = "$upstream_sha" ]; then
|
if [ "$recorded" != "$head_sha" ] && [ "$blob_sha" = "$upstream_sha" ]; then
|
||||||
printf 'NOTICE: %s has moved to %s; the snapshot describes the older %s (stale, not untruthful)\n' \
|
# Do not ASSERT which side is newer — test it. Asserting that HEAD is the
|
||||||
"$ROOT" "${head_sha:0:7}" "${recorded:0:7}" >&2
|
# newer side points the operator at a refresh (which costs a ~67-minute
|
||||||
|
# base rebuild) when the real remedy may be `git pull` in this clone. The
|
||||||
|
# refresh path below already uses this primitive; reuse it here.
|
||||||
|
if git -C "$ROOT" merge-base --is-ancestor "$recorded" "$head_sha" 2>/dev/null; then
|
||||||
|
printf 'NOTICE: %s has moved on to %s; the snapshot describes the older %s (stale, not untruthful — refresh to catch up)\n' \
|
||||||
|
"$ROOT" "${head_sha:0:7}" "${recorded:0:7}" >&2
|
||||||
|
elif git -C "$ROOT" merge-base --is-ancestor "$head_sha" "$recorded" 2>/dev/null; then
|
||||||
|
printf 'NOTICE: %s is BEHIND at %s; the snapshot describes the newer %s — pull this clone, do NOT refresh the snapshot\n' \
|
||||||
|
"$ROOT" "${head_sha:0:7}" "${recorded:0:7}" >&2
|
||||||
|
else
|
||||||
|
printf 'NOTICE: %s (HEAD %s) and the recorded %s have DIVERGED — neither is an ancestor of the other; reconcile the clone before refreshing\n' \
|
||||||
|
"$ROOT" "${head_sha:0:7}" "${recorded:0:7}" >&2
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
printf 'NOTICE: the working tree of %s differs from the snapshot (HEAD %s)\n' \
|
printf 'NOTICE: the working tree of %s differs from the snapshot (HEAD %s)\n' \
|
||||||
"$ROOT" "${head_sha:0:7}" >&2
|
"$ROOT" "${head_sha:0:7}" >&2
|
||||||
|
|||||||
Reference in New Issue
Block a user