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:
+108
@@ -13,6 +13,114 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
**Version audit + three pins moved, one deliberately not moved.** `pi`
|
||||||
|
0.84.4 -> 0.85.1, `mempalace` 3.8.0 -> 3.9.0, `pi-atelier` v0.10.0 -> v0.10.1,
|
||||||
|
and `PI_STUDIO_VERSION` relabelled `none` -> `v0.9.60-rc.0` to record what the
|
||||||
|
floating `main` ref actually resolves to. `PI_FORK_REF=master` stays floating
|
||||||
|
and therefore adopts e69725c. Each rationale is written at the ARG itself
|
||||||
|
rather than only here, because that is where the next person doing the audit
|
||||||
|
will be standing.
|
||||||
|
|
||||||
|
0.85.0 is SKIPPED on purpose: it shipped internal experimental code and extra
|
||||||
|
subpaths that broke SDK imports (upstream #9132), and 0.85.1 exists to undo
|
||||||
|
exactly that. Neither release has a Breaking/Removed changelog heading, the
|
||||||
|
engine floor is unchanged (>=22.19.0 against the container's 22.23.2), and
|
||||||
|
runtime deps drop 20 -> 19.
|
||||||
|
|
||||||
|
The pi bump was verified by RUNNING it, not by reading about it, because this
|
||||||
|
repo has already been burned by a version pair that no changelog flagged
|
||||||
|
(pi-atelier < 0.7.1 hangs pi >= 0.84 at startup with no error). 0.85.1 was
|
||||||
|
side-installed and driven under a pty in five combinations — each companion
|
||||||
|
extension plus atelier v0.10.0 AND v0.10.1 — with a CPU delta of 0.00-0.01s
|
||||||
|
over a 5s window where the known hang signature is ~5s of sustained CPU. The
|
||||||
|
check was two-sided: the atelier sidebar painted ACTIVITY+WORKSPACE markers
|
||||||
|
identically to the 0.84.4 control, so "alive" could be distinguished from
|
||||||
|
"silently absent".
|
||||||
|
|
||||||
|
**NODE_VERSION stays 22 — audited, not overlooked.** node 24 is technically
|
||||||
|
safe: all five prebuilt native addons in pi use NAPI (ABI-stable, no
|
||||||
|
NODE_MODULE_VERSION lock, no binding.gyp), nothing in the image declares a node
|
||||||
|
CEILING, and the install is one token (`setup_${NODE_VERSION}.x`). agent-browser
|
||||||
|
0.36.0 declares `engines.node >=24`, but that field is vestigial for the
|
||||||
|
artifact actually shipped: the image's own 0.35.2 declares the same floor and
|
||||||
|
runs fine on 22.23.2 as a prebuilt aarch64 ELF. The reason to wait is
|
||||||
|
attribution, not compatibility — this release already moves pi a minor,
|
||||||
|
mempalace a minor and bakes a Studio RC, so adding a node major would leave four
|
||||||
|
suspects if the image misbehaves. Worth doing as its own release with the smoke
|
||||||
|
suite as the gate. (v22 is in maintenance until 2027-04-30; v24 is Active LTS
|
||||||
|
to 2026-10-20 and maintained to 2028-04-30, so there is real headroom.)
|
||||||
|
|
||||||
|
mempalace's client bump carries a sequencing note that is now also CORRECT: the
|
||||||
|
comment at the ARG claimed synlig serves 3.7.1 server-side, which was stale.
|
||||||
|
Measured 2026-09-06 over ssh, synlig's uv tool entry last changed 2026-08-25
|
||||||
|
and serves 3.8.0. Client 3.9.0 against server 3.8.0 is accepted skew until
|
||||||
|
synlig's compose stack is redeployed; 3.9.0's headline additions (release
|
||||||
|
awareness, `task create`/`task launch`) are SERVER-side and stay dark until
|
||||||
|
then — a client bump alone cannot light them up.
|
||||||
|
|
||||||
|
**agent-browser was running 7 weeks stale, and the interesting part is why
|
||||||
|
nothing noticed.** The image has shipped 0.35.2 since the last base rebuild,
|
||||||
|
but every session on mbp-m1-2020 was executing 0.27.0 from a 2026-07-17
|
||||||
|
hand-install: `npm i -g` writes into `~/.pi/npm-global`, which is the
|
||||||
|
devbox-pi-config VOLUME, and PATH puts that at position 2 against /usr/bin at
|
||||||
|
position 8. This is the third package hit by that exact hazard (pi itself and
|
||||||
|
pi-atelier already have guards), so the guard is now generalised instead of
|
||||||
|
re-invented a fourth time.
|
||||||
|
|
||||||
|
The damage was not the binary. It was the BUNDLED SKILL, which is the part an
|
||||||
|
agent reads: 3 skillsets / 17.6 KB core in 0.27.0 versus 8 skillsets / 31.5 KB
|
||||||
|
core in 0.35.2, with ten subcommands present in the image and entirely
|
||||||
|
undocumented to the agent (a11y, browser, data, mcp, page, plugin, read,
|
||||||
|
selectors, to, webmcp). A stale tool announces itself with an error; a stale
|
||||||
|
skill just quietly teaches the wrong commands and everything looks fine.
|
||||||
|
|
||||||
|
Three changes, at the three places this can be caught:
|
||||||
|
- `entrypoint-user.sh` retires a volume copy by MOVING it aside (reversible,
|
||||||
|
same instinct as the settings backups) and only when the image ships its own
|
||||||
|
copy, so a machine that deliberately hand-installs on an image without one
|
||||||
|
keeps it. The `bin/` shim is removed too — a dangling symlink would be a
|
||||||
|
worse failure than a stale version.
|
||||||
|
- `scripts/recreate-sanity-check.sh` asserts `agent-browser` resolves under
|
||||||
|
/usr. This is the check that matters, because it runs where the volume is
|
||||||
|
real.
|
||||||
|
- `scripts/smoke-test.sh` gets the build-time half, labelled WEAK in the source
|
||||||
|
for an honest reason: a `docker run` container has an empty config volume, so
|
||||||
|
it can never see the shadowing it is nominally testing for.
|
||||||
|
|
||||||
|
**pi-fork gets a capability floor: `extensions: []`.** Forks were measured
|
||||||
|
twice (2026-09-01, 2026-09-06, 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` — which landed in `wing_pi`, where a
|
||||||
|
wing-scoped `diary_read` never sees it.
|
||||||
|
|
||||||
|
The cause is upstream and by design, so there is nothing to wait for: the child
|
||||||
|
is handed `getHeader()+getBranch()`, i.e. the WHOLE active session branch, with
|
||||||
|
the brief appended as the final user message and the system prompt untouched
|
||||||
|
(pi-fork `src/index.ts`). In a long session the parent narrative simply
|
||||||
|
outweighs the task, and the child does the statistically obvious thing — it
|
||||||
|
continues the story it finds itself inside. Config offers no context knob
|
||||||
|
(extensions, environment, offline, costFooter, effort profiles only).
|
||||||
|
|
||||||
|
Falsified the tempting explanation before acting on it: the failures are NOT a
|
||||||
|
too-small model. The same model as the `fast` profile (haiku, thinking off)
|
||||||
|
obeyed the identical brief perfectly when run as
|
||||||
|
`pi -p --mode json --session-id <fresh> --no-extensions` — correct values,
|
||||||
|
exact format, no session recap, 3 seconds, $0.012. Model held constant, context
|
||||||
|
inheritance removed, failure gone.
|
||||||
|
|
||||||
|
`extensions: []` is therefore a mechanical guarantee rather than an
|
||||||
|
instruction: the mempalace bridge is a pi EXTENSION, so a fork child now runs
|
||||||
|
with `--no-extensions` and cannot write to the shared palace under the parent's
|
||||||
|
identity. Verified by asking a child to enumerate its own tools: `read, bash,
|
||||||
|
edit, write` — no `mempalace_*`, no `recall`, no nested `fork`. Two honest
|
||||||
|
limits, stated so nobody over-trusts this: it removes PALACE writes, not
|
||||||
|
FILESYSTEM writes (`edit`/`write` remain), and it costs forks their palace
|
||||||
|
search and recall. Set the key to `null` to restore normal loading.
|
||||||
|
|
||||||
|
Smoke asserts the floor is `[]` specifically, not merely falsy — `null` is the
|
||||||
|
unguarded state, so a "truthy or not" test would pass on exactly the
|
||||||
|
configuration being guarded against.
|
||||||
|
|
||||||
**`credential-incident-response` §5/§6 corrected — a stated mechanism was wrong,
|
**`credential-incident-response` §5/§6 corrected — a stated mechanism was wrong,
|
||||||
and this is the second time in three days this section named a wrong reason
|
and this is the second time in three days this section named a wrong reason
|
||||||
for a zero.** Docs only.
|
for a zero.** Docs only.
|
||||||
|
|||||||
+20
-7
@@ -450,13 +450,26 @@ ARG INSTALL_MEMPALACE=true
|
|||||||
# the part that should stay manual.
|
# the part that should stay manual.
|
||||||
#
|
#
|
||||||
# Deployment sequencing note for whoever ships this bump: synlig (the shared
|
# Deployment sequencing note for whoever ships this bump: synlig (the shared
|
||||||
# central palace host) currently serves mempalace 3.7.1 SERVER-SIDE via
|
# central palace host) serves mempalace 3.8.0 SERVER-SIDE via
|
||||||
# docker-compose.mempalace.yml, which reuses this same devbox image. Bumping
|
# docker-compose.mempalace.yml, which reuses this same devbox image. (Measured
|
||||||
# this ARG changes only the CLIENT version baked into pi-devbox images: it
|
# 2026-09-06 over ssh: synlig's UV_TOOL_DIR mempalace entry last changed
|
||||||
# introduces client/server skew until synlig's compose stack is separately
|
# 2026-08-25 15:33 — this comment previously said 3.7.1, which was stale.)
|
||||||
# rebuilt/redeployed with the new pin. Not something to code around here —
|
# Bumping this ARG changes only the CLIENT version baked into pi-devbox
|
||||||
# just sequence the redeploy.
|
# images: it introduces client/server skew until synlig's compose stack is
|
||||||
ARG MEMPALACE_VERSION=3.8.0
|
# separately rebuilt/redeployed with the new pin. Not something to code around
|
||||||
|
# here — just sequence the redeploy.
|
||||||
|
#
|
||||||
|
# v1.8.13: 3.8.0 -> 3.9.0. Audited: no Breaking/Removed changelog headings.
|
||||||
|
# Adopted mainly for #2281 (`mempalace_mine` accepts a single conversation
|
||||||
|
# file again) — though note that does NOT unblock this image's own feeder,
|
||||||
|
# which was measured to mine DIRECTORIES, not files, so it was never hitting
|
||||||
|
# that bug. Four behaviour changes ride along and are skew-relevant while
|
||||||
|
# synlig stays on 3.8.0: hub-forward escaping, an HTTP lock split, similarity
|
||||||
|
# score semantics, and parsed-output compatibility. 3.9.0-only features
|
||||||
|
# (release awareness, `task create`/`task launch` MCP tools) are SERVER-side,
|
||||||
|
# so they stay dark until synlig is redeployed — a client bump alone cannot
|
||||||
|
# light them up.
|
||||||
|
ARG MEMPALACE_VERSION=3.9.0
|
||||||
ENV UV_TOOL_DIR=/opt/uv-tools
|
ENV UV_TOOL_DIR=/opt/uv-tools
|
||||||
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
||||||
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
||||||
|
|||||||
+37
-4
@@ -95,7 +95,25 @@ ARG USER_NAME=developer
|
|||||||
# `.agents/skills/<group>/` directories were not discovered, and root Markdown
|
# `.agents/skills/<group>/` directories were not discovered, and root Markdown
|
||||||
# files such as README.md / AGENTS.md inside a skill dir were reported as
|
# files such as README.md / AGENTS.md inside a skill dir were reported as
|
||||||
# broken skills unless they declared valid skill frontmatter.
|
# broken skills unless they declared valid skill frontmatter.
|
||||||
ARG PI_VERSION=0.84.4
|
#
|
||||||
|
# v1.8.13: 0.84.4 -> 0.85.1. SKIP 0.85.0 deliberately — it accidentally
|
||||||
|
# published internal experimental code and extra subpaths, breaking SDK
|
||||||
|
# imports (upstream #9132); 0.85.1 exists specifically to undo that, with the
|
||||||
|
# supported SDK and stdio RPC API unchanged. Audited: no Breaking/Removed
|
||||||
|
# changelog headings in either release, engine floor unchanged (>=22.19.0,
|
||||||
|
# container runs 22.23.2), runtime deps 20 -> 19. User-visible changes are the
|
||||||
|
# streaming indicator moving into the editor border and faster fullscreen
|
||||||
|
# transcript search; no deprecation language anywhere.
|
||||||
|
#
|
||||||
|
# Verified EMPIRICALLY rather than from the changelog, because a pi bump has
|
||||||
|
# hung the TUI before (pi-atelier < 0.7.1 + pi >= 0.84): 0.85.1 was
|
||||||
|
# side-installed and driven under a pty against all four companion extensions,
|
||||||
|
# with atelier v0.10.0 AND v0.10.1 — five combinations, each rendering alive
|
||||||
|
# with a CPU delta of 0.00-0.01s over a 5s window, where the known hang
|
||||||
|
# signature is ~5s of sustained CPU. Two-sided check: the atelier sidebar
|
||||||
|
# painted ACTIVITY+WORKSPACE identically to the 0.84.4 control, so the test
|
||||||
|
# could distinguish "loaded" from "silently absent".
|
||||||
|
ARG PI_VERSION=0.85.1
|
||||||
ARG PI_TOOLKIT_REF=main
|
ARG PI_TOOLKIT_REF=main
|
||||||
ARG PI_EXTENSIONS_REF=main
|
ARG PI_EXTENSIONS_REF=main
|
||||||
# Repo URLs default to the canonical gitea origin but are overridable so a
|
# Repo URLs default to the canonical gitea origin but are overridable so a
|
||||||
@@ -147,9 +165,14 @@ ARG PI_OBSMEM_REF=master
|
|||||||
# the /opt checkout. Adding an install here would be a no-op that only costs
|
# the /opt checkout. Adding an install here would be a no-op that only costs
|
||||||
# build time.
|
# build time.
|
||||||
ARG PI_ATELIER_REPO=https://github.com/michaelmjhhhh/pi-atelier.git
|
ARG PI_ATELIER_REPO=https://github.com/michaelmjhhhh/pi-atelier.git
|
||||||
ARG PI_ATELIER_REF=v0.10.0
|
# v1.8.13: v0.10.0 -> v0.10.1. Refactor-only upstream (formatters, tests,
|
||||||
|
# panel identity); peerDependencies declare pi >=0.84.0, so it spans both the
|
||||||
|
# old and new pin. Included because it was already exercised: the pty matrix
|
||||||
|
# for PI_VERSION above ran atelier v0.10.1 against pi 0.85.1 and painted the
|
||||||
|
# sidebar identically to v0.10.0.
|
||||||
|
ARG PI_ATELIER_REF=v0.10.1
|
||||||
# Human-readable tag PI_ATELIER_REF was resolved from; recorded as a label.
|
# Human-readable tag PI_ATELIER_REF was resolved from; recorded as a label.
|
||||||
ARG PI_ATELIER_VERSION=v0.10.0
|
ARG PI_ATELIER_VERSION=v0.10.1
|
||||||
|
|
||||||
RUN set -e && \
|
RUN set -e && \
|
||||||
# git_fetch_ref: clone-equivalent helper that accepts EITHER a branch name
|
# git_fetch_ref: clone-equivalent helper that accepts EITHER a branch name
|
||||||
@@ -259,7 +282,17 @@ ARG PI_STUDIO_REF=main
|
|||||||
# PI_STUDIO_VERSION is the human-readable tag (e.g. v0.9.36) that PI_STUDIO_REF
|
# PI_STUDIO_VERSION is the human-readable tag (e.g. v0.9.36) that PI_STUDIO_REF
|
||||||
# was resolved from; recorded as a label below for at-a-glance identification.
|
# was resolved from; recorded as a label below for at-a-glance identification.
|
||||||
# Only meaningful for the studio variant (default `none` otherwise).
|
# Only meaningful for the studio variant (default `none` otherwise).
|
||||||
ARG PI_STUDIO_VERSION=none
|
#
|
||||||
|
# v1.8.13: PI_STUDIO_REF stays floating on `main`, which resolves to
|
||||||
|
# v0.9.60-rc.0 — a RELEASE CANDIDATE, not the latest stable (v0.9.59).
|
||||||
|
# Adopted deliberately (JOA, 2026-09-06); recording the label is what makes
|
||||||
|
# that choice visible via `docker inspect` instead of someone discovering an
|
||||||
|
# RC in production later. Of the 15 commits since 0.9.55, one adds an OPT-IN
|
||||||
|
# network binding for the Studio server: that is network-facing and deserves
|
||||||
|
# its own audit before anyone enables it. The container default is unchanged —
|
||||||
|
# pi-studio still hard-binds 127.0.0.1 (see the STUDIO_EXPOSE bridge below),
|
||||||
|
# so adopting the RC does not by itself expose Studio to the LAN.
|
||||||
|
ARG PI_STUDIO_VERSION=v0.9.60-rc.0
|
||||||
RUN if [ "${INSTALL_STUDIO}" = "true" ]; then \
|
RUN if [ "${INSTALL_STUDIO}" = "true" ]; then \
|
||||||
set -e; \
|
set -e; \
|
||||||
rm -rf /opt/pi-studio && mkdir -p /opt/pi-studio && \
|
rm -rf /opt/pi-studio && mkdir -p /opt/pi-studio && \
|
||||||
|
|||||||
@@ -471,6 +471,38 @@ if command -v pi &>/dev/null; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── agent-browser: retire a stale volume copy that shadows the image ───
|
||||||
|
# Same hazard class as the pi-atelier retirement above, different delivery
|
||||||
|
# path — and this block exists because that guard did not generalise.
|
||||||
|
# ~/.pi/npm-global lives on the devbox-pi-config VOLUME, so anything ever
|
||||||
|
# installed there with `npm i -g` survives every image upgrade, and PATH puts
|
||||||
|
# it AHEAD of /usr/bin (position 2 vs 8).
|
||||||
|
#
|
||||||
|
# Measured on mbp-m1-2020, 2026-09-06: a 2026-07-17 hand-install pinned
|
||||||
|
# agent-browser 0.27.0 in the volume while the image shipped 0.35.2, so every
|
||||||
|
# session for ~7 weeks ran a stale CLI. The damaging part was not the binary
|
||||||
|
# but its BUNDLED SKILL, which is what the agent actually reads: 3 skillsets /
|
||||||
|
# 17.6 KB core in 0.27.0 vs 8 skillsets / 31.5 KB core in 0.35.2, with ten
|
||||||
|
# subcommands present in the image and undocumented to the agent (a11y,
|
||||||
|
# browser, data, mcp, page, plugin, read, selectors, to, webmcp). A stale tool
|
||||||
|
# announces itself; a stale skill quietly teaches the wrong commands.
|
||||||
|
#
|
||||||
|
# MOVE rather than delete (reversible, same instinct as the settings backups
|
||||||
|
# above), and only when the image ships its own copy — a machine that
|
||||||
|
# deliberately hand-installs agent-browser on an image WITHOUT one keeps it.
|
||||||
|
_ab_vol="$HOME/.pi/npm-global/lib/node_modules/agent-browser"
|
||||||
|
if [ -d "$_ab_vol" ] && [ -d /usr/lib/node_modules/agent-browser ]; then
|
||||||
|
_ab_park="$HOME/.pi/npm-global/.retired-agent-browser-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
if mkdir -p "$_ab_park" 2>/dev/null && mv "$_ab_vol" "$_ab_park/" 2>/dev/null; then
|
||||||
|
# The bin shim is what PATH actually hits; leaving it behind would give a
|
||||||
|
# dangling symlink, which is a worse failure than a stale version.
|
||||||
|
rm -f "$HOME/.pi/npm-global/bin/agent-browser" 2>/dev/null || true
|
||||||
|
echo "agent-browser: retired stale volume copy -> ${_ab_park} (image copy now wins; delete the parked dir when satisfied)"
|
||||||
|
else
|
||||||
|
echo "WARN: agent-browser: stale volume copy at $_ab_vol shadows the image copy and could not be moved; retire it by hand"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# ── pi-studio: optional loopback bridge (opt-in) ──────────────────────
|
# ── pi-studio: optional loopback bridge (opt-in) ──────────────────────
|
||||||
# pi-studio binds its server to 127.0.0.1 inside the container, which a
|
# pi-studio binds its server to 127.0.0.1 inside the container, which a
|
||||||
# published Docker port cannot reach. When STUDIO_EXPOSE is truthy (set in
|
# published Docker port cannot reach. When STUDIO_EXPOSE is truthy (set in
|
||||||
|
|||||||
@@ -374,6 +374,34 @@ if [ -f "$HOME/.pi/agent/settings.json" ]; then
|
|||||||
fi
|
fi
|
||||||
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 ─────────────────────────────
|
# ── pi <-> pi-atelier compatibility floor ─────────────────────────────
|
||||||
# atelier < 0.7.1 wraps pi's private TUI renderer in a way that recurses under
|
# 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
|
# 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)" \
|
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'
|
'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 ────────────────────────
|
# ── /tmp/sshcm directory created by entrypoint ────────────────────────
|
||||||
exec_test "/tmp/sshcm dir mode 700 (ssh ControlMaster)" \
|
exec_test "/tmp/sshcm dir mode 700 (ssh ControlMaster)" \
|
||||||
'test -d /tmp/sshcm && [ "$(stat -c %a /tmp/sshcm)" = "700" ] && echo ok'
|
'test -d /tmp/sshcm && [ "$(stat -c %a /tmp/sshcm)" = "700" ] && echo ok'
|
||||||
|
|||||||
Reference in New Issue
Block a user