diff --git a/CHANGELOG.md b/CHANGELOG.md index b97eff3..c9b1a32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`). --- -## Unreleased +## v1.8.11 — 2026-08-27 **Shell state that the writable layer eats on every recreate now gets rebuilt at start.** Two additions to `entrypoint-user.sh`, both idempotent, both silent @@ -78,6 +78,104 @@ the v1.8.0 mistake of a smoke assertion written against a stage that does not exist at run time. `workflow_dispatch` with `smoke_only` remains the way to exercise this against `HEAD` before a tag. +**Also carried by the floating `mempalace-toolkit` main ref** (resolved at build +time, not by a pi-devbox commit — `MEMPALACE_TOOLKIT_REF=main`): + +**A scrubbed re-export of a dormant session could silently never reach the +palace host.** `bin/mempalace-pi-session` ships to the palace with +`rsync -a --update`, and the stage file's mtime is deliberately the SOURCE +transcript's mtime (`os.utime()`, "preserve session mtime for dedup +stability"). Re-exporting a session that has not been appended to since its +last ship therefore produces a mtime that is *not newer* than the receiver's — +exactly the case a redactor upgrade needs to ship, since content differs while +mtime does not. `--update` reported success and sent nothing. Found and +patched by `pi@mbp-m1-2020` (mempalace-toolkit `a361b71`): `--update` → +`--checksum`, which compares content and ignores size/mtime entirely. +Dropping `--update` outright was considered and rejected — rsync's default +quick check already transfers on a size difference alone, which would have +masked the *next* instance of this (a redaction whose placeholder happens to +match the secret's length) as fixed. `os.utime()` is untouched; its backdating +is a separate, load-bearing design call for dedup stability. New regression +test, `scripts/test-rsync-ship-idempotency.sh`, runs fully offline (a local +rsync destination exercises the same size/mtime/checksum comparison as the ssh +transfer) and is built to *discriminate*: it must fail against `--update` and +pass against `--checksum,` not merely exercise the code path — the first draft +of the test used fixture strings of different lengths and passed for the wrong +reason (rsync's quick check transfers on size difference alone regardless of +`--update`), which is the same trap the patch itself was written to avoid. +**Acceptance line for this class of change going forward:** "receiver sha256 +matches sender for every staged file", not "local stage is clean" — a clean +local stage says nothing about what a dormant session already sent. + +**An event addressed to an identity no session runs as is delivered to +nobody, and this fleet has now hit it three separate ways.** RFC 003 gains +§7.13 and open-decision 10 (mempalace-toolkit `21023e7`, docs only, no image +behaviour change): the owed-set derivation — the log's only push channel — is +keyed on `to_agent`, and a reply is always addressed back to whatever string +the *original writer* put in `from_agent`. Nothing validates that string +against a live session identity, so authoring under a synthetic or foreign +name makes every reply to that event write-only. Measured cost this cycle: a +directed ask planted under a synthetic sender drew a correct reply containing +an urgent security finding, and it sat unread for ~2h20m, found only because a +human asked whether mail had arrived. Permitted exception, unchanged: a +synthetic sender is fine for a deliberate control experiment, provided the +body names the real identity to reply to. + +**Also carried by the live `skillset` mount** (each device's own clone, not +baked — except the `mempalace` skill's fallback snapshot, re-vendored below): + +**The mermaid-diagrams checker's cut gate moved from client pixels to a +per-SVG user-space unit.** `CUT_PX` was calibrated against one live page at +one render scale; sweeping `--viewport` 500→1600 on an *unchanged* document +moved the worst overflow −1.0px → −3.0px, near-proportional to the viewport, +i.e. a constant geometric overflow viewed through a changing scale. `cutU = +cutPx / scale` (scale taken per-SVG, never a page average — one page mixes +scales 0.643–0.988) recovers that invariant: the sweep now collapses to +exactly −3.0u at every width. Re-deriving the threshold against the live host +surfaced a real false negative the old pixel gate had: a label at +`cutPx=0.4, scale=0.678` read as healthy under `CUT_PX=0.5` but is `0.59u` — +a genuine cut hiding behind a compressed render scale. `CUT_U` stays `0.5`; +`cutPx` and `scale` are still printed on every issue so a devtools ruler still +confirms the number on the actual page. A new, explicitly-deferred finding +from the same review: `cut` only measures vertically, so an unbreakable token +wider than its box (a long URL, a `snake_case` identifier) is invisible to +soft-wrap, tall, *and* cut simultaneously — filed as a backlog item, not +implemented, pending a fifth acceptance control. + +**The `from_agent`-identity finding above is also now in the `mempalace` +skill itself** ("Writing to another machine", and Anti-Patterns), and the +baked fallback snapshot of that skill was refreshed to match +(`vendor-mempalace-skill.sh`, `6eb20af` → `a12fe5e`) — sanctioned to skip on +its own (`--check` reported stale-but-truthful), done anyway because this +release's point is getting today's fixes live, and the base rebuild below was +already forced regardless. + +### Dependency audit (2026-08-27) + +Every component checked against upstream by direct command, not assumed: + +| Component | Baked in v1.8.10 | Upstream now | Action | +|---|---|---|---| +| **mempalace-toolkit** | `b2b50af` | **`21023e7`** | ships the rsync ship-fix + RFC 003 §7.13 (both above) | +| **skillset** (mempalace fallback snapshot) | `6eb20af` | **`a12fe5e`** | re-vendored (above); live-mounted devices already had it | +| pi | `0.84.3` (pinned) | `0.84.3` is npm latest | none | +| mempalace | `3.8.0` (pinned) | `3.8.0` is PyPI latest | none | +| pi-atelier | `v0.8.2` (pinned) | `v0.8.2` highest tag | none | +| pi-studio (studio variant) | `v0.9.52` | `v0.9.52` — `main`'s commit and the tag's commit are identical (0 either direction) | none | +| pi-toolkit | `0e1369e` | `0e1369e` (local clone HEAD == `origin/main`) | none | +| pi-extensions | `2022887` | `2022887` (local clone HEAD == `origin/main`) | none | +| pi-fork | `bf702b4` | `bf702b4` | none | +| pi-observational-memory | `ce9fc98` | `ce9fc98` | none | + +pi-toolkit / pi-extensions checked against their actual Gitea origin (the +Dockerfile's `PI_TOOLKIT_REPO` / `PI_EXTENSIONS_REPO`), not a GitHub mirror — +querying `api.github.com` for those two returned nothing (rate-limited or +blocked; not investigated, the local clones are the source of truth anyway). +No SHA above is fork-supplied; a fork inventing plausible-looking upstream SHAs +is a recorded failure mode (v1.8.9), so every value here came from +`git ls-remote`, a local clone's own `origin/HEAD`, `npm view`/registry JSON, +or the PyPI JSON API, run directly. + --- ## v1.8.10 — 2026-08-27