release: audited bumps (pi 0.85.1, mempalace 3.9.0, atelier v0.10.1) + two guards
Version audit for the next release. pi 0.84.4 -> 0.85.1, deliberately skipping 0.85.0 (it published internal experimental code and broke SDK imports, upstream #9132). mempalace 3.8.0 -> 3.9.0. pi-atelier v0.10.0 -> v0.10.1. PI_STUDIO_VERSION relabelled none -> v0.9.60-rc.0 so the floating main ref's RC status is visible at docker-inspect time instead of discovered later. PI_FORK_REF stays floating and adopts e69725c. The pi bump was verified by running it under a pty in five combinations rather than by reading the changelog, because this repo has already shipped a version pair no changelog flagged (atelier < 0.7.1 hangs pi >= 0.84). CPU delta 0.00-0.01s over 5s against a ~5s sustained-CPU hang signature, two-sided via the atelier sidebar painting identically to the 0.84.4 control. NODE_VERSION stays 22 on purpose: node 24 is technically safe (pi's five prebuilt addons are all NAPI, nothing declares a ceiling, agent-browser's engines.node >=24 is vestigial for the shipped aarch64 ELF), but this release already moves two minors and bakes an RC, and a node major would leave four suspects if the image misbehaves. Own release, smoke suite as the gate. Also corrects a stale claim at the mempalace ARG: synlig serves 3.8.0 server-side, not 3.7.1 (measured over ssh 2026-09-06). agent-browser volume shadowing: the image has shipped 0.35.2, but every session on mbp-m1-2020 ran 0.27.0 from a 2026-07-17 hand-install in ~/.pi/npm-global (a VOLUME, at PATH position 2 vs /usr/bin at 8). Third package hit by this hazard after pi and pi-atelier, so the guard is now generalised: entrypoint-user.sh retires the copy by moving it aside (reversible, only when the image ships its own), recreate-sanity-check.sh asserts resolution under /usr where the volume is real, smoke-test.sh carries the build-time half and says in the source why it is weak. The real damage was the stale BUNDLED SKILL (3 skillsets/17.6 KB vs 8/31.5 KB, ten subcommands undocumented to the agent) - a stale tool errors, a stale skill quietly teaches wrong commands. pi-fork capability floor (extensions: []): forks were measured across four dispatches ignoring their brief, answering in the user's voice, fabricating self-referential measurements, and once filing a diary entry as agent_name=pi. Cause is upstream by design - the child gets getHeader()+getBranch(), the whole active session branch, with the brief as the final user message. Not a model-capability problem: the same model as the fast profile obeyed the identical brief perfectly with a fresh session and no inherited context. extensions: [] runs children with --no-extensions, so the mempalace bridge is absent and palace writes are impossible by construction (verified by asking a child to enumerate its tools: read, bash, edit, write). Removes palace writes, not filesystem writes.
This commit is contained in:
@@ -374,6 +374,34 @@ if [ -f "$HOME/.pi/agent/settings.json" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── agent-browser must resolve to the image, not the config volume ────
|
||||
# The same volume-shadowing hazard already asserted for pi (above) and
|
||||
# pi-atelier (just now), for the third package it has bitten. This check
|
||||
# belongs HERE rather than only in smoke-test.sh: a build-time container has an
|
||||
# empty ~/.pi/npm-global, so smoke-test can never see the stale copy that a
|
||||
# real recreate inherits. Measured instance: 0.27.0 from 2026-07-17 shadowed
|
||||
# the image's 0.35.2 for ~7 weeks on mbp-m1-2020, silently supplying an older
|
||||
# BUNDLED SKILL (3 skillsets vs 8) — the agent read the stale instructions
|
||||
# without any version mismatch ever being surfaced.
|
||||
AB_PATH=$(command -v agent-browser 2>/dev/null || true)
|
||||
if [ -z "$AB_PATH" ]; then
|
||||
warn "agent-browser not on PATH (expected in v1.6.0+ images; skipping shadow check)"
|
||||
else
|
||||
AB_REAL=$(readlink -f "$AB_PATH" 2>/dev/null || echo "$AB_PATH")
|
||||
AB_VER=$(agent-browser --version 2>/dev/null | head -n1)
|
||||
case "$AB_REAL" in
|
||||
/usr/*)
|
||||
pass "agent-browser resolves to the image copy (${AB_VER:-version unknown})"
|
||||
;;
|
||||
*)
|
||||
fail "agent-browser resolves to $AB_REAL (${AB_VER:-version unknown}) — a ~/.pi/npm-global VOLUME copy is shadowing the image; the entrypoint retirement guard did not run or could not move it"
|
||||
;;
|
||||
esac
|
||||
if [ -d "$HOME/.pi/npm-global/lib/node_modules/agent-browser" ]; then
|
||||
fail "stale agent-browser still present in the ~/.pi/npm-global volume (entrypoint guard did not retire it)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── pi <-> pi-atelier compatibility floor ─────────────────────────────
|
||||
# atelier < 0.7.1 wraps pi's private TUI renderer in a way that recurses under
|
||||
# pi >= 0.84: pi hangs at startup burning CPU, with no error message. atelier's
|
||||
|
||||
@@ -718,6 +718,34 @@ exec_test "pi-atelier registered in packages[] (TUI sidebar)" \
|
||||
exec_test "pi-atelier registered from /opt, not npm: (volume-shadowing guard)" \
|
||||
'jq -e "((.packages // []) | any((type == \"string\") and endswith(\"/pi-atelier\"))) and (((.packages // []) | any(. == \"npm:pi-atelier\")) | not)" $HOME/.pi/agent/settings.json'
|
||||
|
||||
# agent-browser: the third package hit by ~/.pi/npm-global volume shadowing
|
||||
# (after pi itself and pi-atelier). This build-time check is deliberately WEAK
|
||||
# and says so: a `docker run` container has an EMPTY config volume, so it can
|
||||
# only prove the image ships a sane copy and nothing in the image itself
|
||||
# shadows it. The check that actually bites lives in
|
||||
# recreate-sanity-check.sh, which runs where the volume is real — that is
|
||||
# where a 7-week-old 0.27.0 was caught shadowing 0.35.2 on 2026-09-06.
|
||||
exec_test "agent-browser resolves under /usr (volume-shadowing guard, build-time half)" '
|
||||
p=$(command -v agent-browser) || { echo "agent-browser not on PATH" >&2; exit 1; }
|
||||
r=$(readlink -f "$p")
|
||||
echo "resolved=[$r] version=[$(agent-browser --version 2>/dev/null | head -n1)]" >&2
|
||||
case "$r" in /usr/*) ;; *) exit 1 ;; esac
|
||||
test ! -d "$HOME/.pi/npm-global/lib/node_modules/agent-browser" || exit 1
|
||||
echo ok
|
||||
'
|
||||
|
||||
# pi-fork capability floor. `extensions: []` makes a fork child run with
|
||||
# --no-extensions, which is the only MECHANICAL guarantee that a fork cannot
|
||||
# file drawers or diary entries under the parent's identity — the mempalace
|
||||
# bridge is an extension, so removing extensions removes the write path.
|
||||
# Asserted because it is a security-shaped default that a settings merge or a
|
||||
# hand-edit could silently drop, and its absence is invisible until a fork
|
||||
# writes to the shared palace as you (measured twice: 2026-09-01, 2026-09-06).
|
||||
# Deliberately compares to [] and not "is falsy": null means "load normal
|
||||
# extensions", i.e. exactly the unguarded state this asserts against.
|
||||
exec_test "pi-fork extensions floor is [] (forks cannot write to the palace)" \
|
||||
'jq -e ".[\"pi-fork\"].extensions == []" $HOME/.pi/agent/settings.json'
|
||||
|
||||
# ── /tmp/sshcm directory created by entrypoint ────────────────────────
|
||||
exec_test "/tmp/sshcm dir mode 700 (ssh ControlMaster)" \
|
||||
'test -d /tmp/sshcm && [ "$(stat -c %a /tmp/sshcm)" = "700" ] && echo ok'
|
||||
|
||||
Reference in New Issue
Block a user