f7e35962d681215fd7a9dee2221431dc5532fbd1
196 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f7e35962d6 | docs(changelog): mailbox owed-list fix from mempalace-toolkit c04d488 | ||
|
|
b06941d714 |
firstboot skill: say what to do when a command gate blocks a probe
erdc-guard (fleet extension, not in the image) judges the text of every bash statement. Measured with its own assess(): ERDC_GUARD=off on a child pi does not help (the parent blocks the prompt text, 0.95); pi -p @file is not flagged; cat heredocs are flagged depending on contents; /guard off is volume-wide and keeps confirm-destructive stood down. Order: write tool, @file, /tmp scratch, /guard off last. A blocked probe is a cannot-run. |
||
|
|
00345d3536 |
changelog: confirm-destructive steps aside for another bash gate (pi-extensions 7237855)
The next image bakes pi-extensions main, which now has 7237855. With a second
command gate loaded (the fleet's erdc-guard), confirm-destructive still asked
about every command that gate let through, with no time limit. It now skips its
bash check while another extension holds the claim
Symbol.for("pi-extensions.bash-gate"). Its /new, /resume and /fork questions
are unchanged, and with no claim it behaves as before.
check-doc-drift.sh check 9 (every moved component named above the last release
heading) passes: pi-extensions 4c1d28d -> 7237855.
|
||
|
|
057827a84b |
skills: trim two descriptions to a routing sentence, and assert their pointers
Every skill's name and description sit in the system prompt of EVERY request -- that block is the routing index -- while bodies are fetched on demand through the read tool. Measured on this device: 24 discoverable skills cost 10,609 description chars (~2,652 tokens) per request, against 474,656 chars of bodies (~119k tokens) that cost nothing until used. Bodies are 45x the descriptions, which is what the spec's 1024-char description limit is protecting. credential-incident-response (1022, two characters under the limit) and pi-devbox-environment (923) were each carrying a table of contents of what their body covers. Trimmed to what a description is actually for -- answering "should I load this?" -- reclaiming 1,087 chars (~271 tokens) of permanent context. 4,154 -> 3,067 across the five baked skills. WHY ONLY THESE TWO. Both are named BY PATH from the always-loaded managed block, so neither depends on description-matching to be found. pi-extensions is left alone deliberately: check-skill-floor.sh compares the vendored copy to the package repo by DIRECTORY HASH, so trimming ours alone registers as drift and turns that gate red; and it is the one skill that also ships in the skillset repo, where it runs on hosts with no pi-devbox managed block and genuinely needs its description. pi-devbox-firstboot stays fuller than needed because it is the skill a device with nothing else has to find. NOTHING WAS DELETED, ONLY RELOCATED -- and that was checked rather than assumed: all 18 enumerated items were confirmed present in the bodies BEFORE cutting (frontmatter stripped, so the description's own line could not score its own claim), and `git diff --stat` confirms both bodies are byte-identical. THE TRIM CREATES A DEPENDENCY, SO IT IS NOW ASSERTED. smoke-test.sh checks both pointers by path, next to the firstboot one. The premise of the cut is "the block names them", which holds only while those lines exist -- reword the block, drop a pointer, and the skill silently stops being reachable for a device that did not already know it was there. Verified discriminating: a reworded block fails the grep. An instrument failure caught on the way, and the reason the coverage check was re-run: my first pass used `grep -E 'a\|b'`, where \| is a LITERAL pipe in ERE, so 8 of 18 items read as 0 hits. Every zero was a pattern containing \| and every non-zero lacked one -- that correlation, not the content, was the finding. With real alternation all 18 are present. Loader re-verified on both trimmed skills: rc=0, no skill diagnostics. |
||
|
|
206fd1ec35 |
ci: gate baked skill frontmatter against the loader's own limits
Found while adding pi-devbox-firstboot, whose description was drafted at 1056 characters against pi's MAX_DESCRIPTION_LENGTH of 1024. pi does not reject it: dist/core/skills.js pushes a diagnostic and then loads the skill anyway -- "Still load the skill even with warnings, unless description is missing or empty". So the skill works, the violation is real, and the only thing that would ever report it is a diagnostic inside a live pi session. Nothing in CI starts one, so the warning is invisible here by construction. A spec violation that still works is the most durable kind of rot: nothing forces the fix. Unlike check-skill-floor.sh, this was NOT free to add. Measured headroom on the five baked skills: credential-incident-response 1022 (2 chars spare), pi-extensions 1003 (21), pi-devbox-firstboot 952, pi-devbox-environment 923, mempalace 254. Two of five are within one sentence of the limit, so "nobody would write a description that long" is already false here. It also catches the sibling failure that is a silent DROP rather than a warning: a missing or empty description returns skill: null, so the file ships, the symlink resolves, smoke-test's `test -f` passes, pi-devbox-version lists it -- and no agent can ever see it. Name rules are checked too, including against the directory, because the loader falls back to parentDirName and a mismatch silently renames the skill while every path-based pointer (the managed AGENTS block included) keeps naming the directory. Truth table, each mutation verified to differ materially from the shipped tree: shipped 0; over-long description 1; missing description 1 (reported as fatal, not a mere violation); name != directory 1; no frontmatter 1; directory with no SKILL.md 1; absent skills tree 2. Exit codes match lint-shell.sh, check-skill-floor.sh and check-doc-drift.sh -- a gate that cannot run must not pass. python3-yaml rather than grep/awk because `description: >-` is a folded block scalar whose value spans lines and joins with spaces, so a line-oriented length check measures the wrong string. Same package the actionlint job already installs, same parser check-doc-drift.sh already uses. The vendored copies (pi-extensions, mempalace) are deliberately in scope: an upstream edit over the limit turns this red on a file this repo does not author, which is precedented by check-skill-floor.sh's coupling and correct because the image ships those bytes. The fix in that case is to refresh the snapshot, not to exempt the file. |
||
|
|
5d6ff537c4 |
skills: add pi-devbox-firstboot, and point the managed AGENTS block at it
The fleet's first-boot acceptance round is now scoped by published state (policy v2, artifact art_20261023T204306_b388e93c3d64): a booting container reads two claim keys -- firstboot-release-<TAG> and firstboot-image-<TAG>-<ARCH> -- and derives whether it owes the release tier, the per-architecture image tier, or only the device tier. Nothing delivered that procedure to a device that does not already have a coordination chain. The gate deliberately needs no roster, because fleet membership changes in both directions, so no message addresses a new device and nothing enrolls it. The consequence is that a BRAND-NEW device -- empty mailbox, no chain, and the mempalace skill correctly telling it that an empty mailbox is a fine answer -- had no path to the round at all. Historically a peer planted the newcomer's first ask cross-device, which is exactly the roster dependency the state design removes. This skill is how a device learns to plant its own. The skill is a FINDER, not the policy: it locates the newest policy.update on topic=firstboot-acceptance and fetches the artifact, and says plainly that the artifact wins on disagreement. A policy baked into an image is pinned to that image; keeping only the lookup here means a revision needs no release and a device on an older image still reaches the current policy. Baked canonical, like pi-devbox-environment, rather than skillset-owned. The round verifies the image, so pinning it to the image is right -- and a skillset-owned skill needs the vendoring machinery (ARG SKILLSET_SNAPSHOT_REF, the manifest/label pair, a per-skill refresh script) that today exists only for mempalace. No wiring was needed: entrypoint-user.sh and pi-devbox-version both enumerate the baked skills directory. smoke-test.sh asserts the AGENTS.md pointer BY PATH, not by prose. A new device reaches the round only through that line, so rewording the paragraph must not be able to cut it silently. Both skill-resolution loops gain the new name. |
||
|
|
779efbf4fe |
ci: pin the smoke image with a running container during the test
The label only protects against reclaims that honour it. The runner hosts also run jobs we do not control: runner-2 has a second act_runner registered with gitea.egl.lan on the same Docker daemon, and opencode-devbox's workflows run raw `docker system prune -af` in 7 places. No prune removes an image a running container uses, so the smoke step now starts a holder (`ci-reclaim-docker.sh hold`: --rm, --network none, sleep 3600) right after the preflight, and the always() cleanup step runs `release` (holder + image). Unprotected now: only the seconds between the build step's load and the holder starting. Verified on an isolated docker:29-dind: an unlabelled held image survives raw system prune and image prune -af (control without holder: deleted); hold fails on a missing image; holder is AutoRemove + network none; release removes holder and image; reclaim behaviour unchanged. |
||
|
|
e0611c787f |
ci docs: the shared daemon is per runner (runner-a1 runs several jobs at once)
Task logs of run 740: runner-a1 received task 2721 (smoke-studio) at
22:53:02Z and task 2722 (build-variant) at 22:58:38Z while 2721 was still
running. Other runners (runner-1/2/3) have capacity 1.
|
||
|
|
12e66aa52e |
ci: stop disk reclaim from deleting a smoke image mid-test
v1.12.2 run 740: build-variant's `docker system prune -af --volumes` untagged pi-devbox:smoke-studio 30 ms after smoke-studio loaded it, so every studio assertion failed and the -studio tags were not published. All jobs share the runner's Docker daemon, and a smoke image is unused between smoke-test.sh's one-shot `docker run`s. - scripts/ci-reclaim-docker.sh: the same reclaim, per object type, but never prunes an image labelled se.jordbo.pi-devbox.ci-smoke-image; labelled orphans are swept after 6h. `assert-protected IMG` is the smoke preflight; `lint WORKFLOW...` rejects raw system/image prunes. - smoke jobs: label their image, assert it, tag it per run (pi-devbox:smoke[-studio]-<run_id>), remove it in an always() step. - all five reclaim sites call the script; lint.yml runs the guard. - Job order unchanged: studio jobs still gate only the -studio tags. Verified on an isolated docker:29-dind (old prune deletes a labelled image; new reclaim keeps it, removes unlabelled + 1970-created labelled images, spares an in-use image, no errors); actionlint, shellcheck, lint-shell, check-workflow-shell, check-doc-drift all clean. |
||
|
|
fb2cb2305c |
release(v1.12.2): pi 1.4.0, pi-studio v0.11.1, Node 24.22.1 + git-lfs 3.8.1 security
Lint / skill-floor (push) Successful in 8s
Lint / hadolint (push) Successful in 10s
Lint / actionlint (push) Successful in 23s
Lint / doc-drift (push) Successful in 14s
Publish Docker Image / lint-gate (push) Successful in 24s
Publish Docker Image / resolve-versions (push) Successful in 12s
Publish Docker Image / base-decide (push) Successful in 10s
Publish Docker Image / build-base (push) Successful in 42m52s
Publish Docker Image / smoke (push) Successful in 5m33s
Publish Docker Image / build-variant (push) Successful in 21m39s
Publish Docker Image / promote-base-latest (push) Successful in 11s
Publish Docker Image / update-description (push) Successful in 12s
Publish Docker Image / smoke-studio (push) Successful in 5m19s
Publish Docker Image / build-variant-studio (push) Successful in 22m9s
Patch release. pi 1.3.0 -> 1.4.0 (audited, notes next to ARG PI_VERSION in
Dockerfile.variant). pi-studio v0.10.2 -> v0.11.1 (-studio tags; pane echo
now defaults to Summary, see Upgrade notes). The base rebuilds because
rootfs/ changed (
|
||
|
|
8ae1659273 |
setup-lan-access: Include ~/.ssh/config even if it appears after start
Same rule as
|
||
|
|
3b1c67a9bf |
release(v1.12.1): ssh-lan.conf Include fix, pi-studio v0.10.2, gitea-mcp v1.10.0
Lint / skill-floor (push) Successful in 11s
Lint / hadolint (push) Successful in 27s
Lint / doc-drift (push) Successful in 28s
Lint / actionlint (push) Successful in 37s
Publish Docker Image / lint-gate (push) Successful in 22s
Publish Docker Image / resolve-versions (push) Successful in 12s
Publish Docker Image / base-decide (push) Successful in 9s
Publish Docker Image / build-base (push) Successful in 42m39s
Publish Docker Image / smoke (push) Successful in 5m24s
Publish Docker Image / smoke-studio (push) Successful in 8m23s
Publish Docker Image / build-variant (push) Successful in 16m59s
Publish Docker Image / update-description (push) Successful in 16s
Publish Docker Image / promote-base-latest (push) Successful in 18s
Publish Docker Image / build-variant-studio (push) Successful in 17m33s
Patch release. The fix itself is
|
||
|
|
c693aa341d |
setup-lan-access: Include ssh-lan.conf even if it is created after start
The Include for ~/.config/devbox-shell/ssh-lan.conf was written only if the
file was readable at container start. A file created afterwards was visible in
the container and silently ignored. Hit on EMB-3HXGNXJ0 on 2026-10-18: the
tor-ms22 override was skipped, ssh fell through to the Mac's passphrase key,
and BatchMode got "Permission denied (publickey,...)" until the script was
rerun by hand.
The line is now always written. Measured with this image's OpenSSH 10.0p2,
expectations written before each run:
- file missing, and also its parent directory missing: Include skipped, rc 0
- file created after the config was written: read on the next ssh, no rerun
- a directory at that path: ignored, rc 0
- file present but unreadable: ssh aborts EVERY connection ("Can't open user
config file"). So that case is still left out, now with a warning on stderr.
- world-writable file: "Bad owner or permissions". The old code Included that
case too, so nothing changes there.
When the file exists at start, the generated config is byte-identical to
v1.12.0's. When it is absent, the only difference is the added Include block.
scripts/smoke-test.sh: two new checks, "Included even if created after start"
and "unreadable left out". Run here, the way exec_test runs them: against the
v1.12.0 script the first FAILs and the second PASSes; against this one both
PASS. README "Naming LAN peers": creating the file no longer needs a restart.
CHANGELOG: Unreleased > Fixed.
lint-shell: clean. check-doc-drift still fails, as it already did on HEAD:
pi-toolkit 3760d90->7c05901 and pi-studio 76db01f->4b4abd1 have moved upstream
since v1.12.0 and are not named in the CHANGELOG. That is release prep and is
not addressed here.
|
||
|
|
ea6605bfae |
release(v1.12.0): pi 1.3.0, mempalace 3.11.0, pi-atelier v0.16.0
Lint / hadolint (push) Successful in 11s
Lint / skill-floor (push) Successful in 13s
Lint / doc-drift (push) Successful in 14s
Lint / actionlint (push) Successful in 24s
Publish Docker Image / lint-gate (push) Successful in 27s
Publish Docker Image / resolve-versions (push) Successful in 11s
Publish Docker Image / base-decide (push) Successful in 15s
Publish Docker Image / build-base (push) Successful in 55m24s
Publish Docker Image / smoke-studio (push) Successful in 6m45s
Publish Docker Image / smoke (push) Successful in 9m1s
Publish Docker Image / build-variant-studio (push) Successful in 18m17s
Publish Docker Image / build-variant (push) Successful in 16m56s
Publish Docker Image / update-description (push) Successful in 8s
Publish Docker Image / promote-base-latest (push) Successful in 11s
Every component at its newest upstream release; each pinned one audited against this image before tagging, none breaking. - ARG PI_VERSION 1.2.0 -> 1.3.0. No breaking-changes section. pi-agent-core, pi-tui and dist/core/extensions are byte-identical to 1.2.0. The only removed export (parseModelPattern, not index-exported) and pi-ai's dropped assistant-message-frame re-export have 0 consumers; all 54 runtime-imported names across the stack and /opt/fleet-pi resolve (negative control reported). With a copy of tor-ms22's ~/.pi/agent: pi -p exits 0 in 10 s, all extensions load, bridge 45 tools, /om:* and /studio offered, atelier paints in both renderers (-ne controls: none). - ARG MEMPALACE_VERSION 3.10.0 -> 3.11.0. init --yes still writes config.json; a 3.10.0-built palace opens, searches and serves MCP under 3.11.0, re-mines its convo file once, then skips it; stdio MCP 45 -> 47 tools, none removed. research() removal: 0 callers. synlig hub stays 3.10.0 (newer-client direction); upgrade notes in CHANGELOG. - ARG PI_ATELIER_REF/VERSION v0.15.0 -> v0.16.0 (3b42825). Additive (keyValues rows, progress bars, two roles, meter background). Protocol and pi floor unchanged; fleet:todos panel renders a real todo on both versions; the meter-background escape tells the two arms apart. - Base rebuilds anyway, so BASE_REBUILD_DATE marker refreshed (uv 0.13.1, gitea-mcp 1.9.0, Node 24.22.0 re-resolve). - CHANGELOG: Unreleased -> v1.12.0 — 2026-10-18. README pin table (and "Three" -> "Four" components), sanity-check example; DOCKER_HUB.md's stale "29 mempalace_* tools". Local gates: check-doc-drift rc 0 (check 9 names pi, mempalace, atelier, pi-extensions), lint-shell, check-workflow-shell, test-link-pi-models 13/13, test-link-pi-extensions 16/16, directive guard, skill floor. |
||
|
|
a6abe17edc |
entrypoint: link fleet pi extensions from the mounted config dir
pi auto-loads every .ts in ~/.pi/agent/extensions/, which lives in the devbox-pi-config volume. A baked extension only seeds a fresh volume once, and a hand-copied one is a per-device step that drifts — the same two problems link-pi-models.sh was added for on 2026-10-12, one level down. This is also the only path that reaches every host: four of them mount common/pi from docker-compose-repo, and three cannot be reached from a devbox to create a symlink by hand (measured: no ssh config entry, no name resolution, and this instance's ~/.ssh-local/config carries no jump alias — only gitea and the mempalace feed host are reachable). link-pi-extensions.sh links each TOP-LEVEL *.ts from the mounted fleet directory (default /opt/fleet-pi, the same directory that already carries models.json; PI_EXTENSIONS_SRC overrides, =off disables). A symlink, not a single-file bind mount, for the measured reason already documented for models.json: git pull replaces a file by rename, and a file mount then serves stale content. Never destructive — a real file of that name in ~/.pi/agent/extensions/ wins over the fleet copy. Nested *.ts are deliberately not linked: the directory is fleet pi config, not an extension tree. - scripts/test-link-pi-extensions.sh: 16 two-sided cases (the negative half is the contract), all passing, wired into lint.yml beside the models one. - smoke-test.sh and recreate-sanity-check.sh gained the matching in-image and on-device checks, each gated on the fleet directory actually holding *.ts so a device without a shared directory is not judged by them. - README: the shared directory can carry extensions, and the rules that apply. Also repairs a pre-existing release blocker, found while checking this change. check-doc-drift.sh failed on pristine HEAD (verified in a clean worktree): pi-extensions main moved 0d8e417 -> 4c1d28d without the SHA being named above the v1.11.2 heading, and docker-publish.yml's resolve-versions has needs: [lint-gate] — so the next tag could not have published. The CHANGELOG now names 4c1d28d and describes the two confirm-destructive commits from their diff (they stop the gate prompting on harmless redirects, and add tee/dd of= as write forms); node --test test/confirm-destructive.test.mjs at 4c1d28d: 2 pass, 0 fail. Local verification: lint-shell.sh (21 files, severity error), check-workflow-shell.sh, check-skill-floor.sh, check-dockerfile-directives.sh, check-doc-drift.sh, and both link-pi-*.sh contract suites all pass. actionlint and hadolint are not installed in a devbox, so those CI steps are unvalidated here; the smoke-test addition runs only inside the image at release time. |
||
|
|
5becbba6c8 |
docs: Studio's PDF export needs LaTeX, in AGENTS.md and Dockerfile.base too
AGENTS.md ("What we DON'T install") and two comments in Dockerfile.base's
typst block still said pi-studio's PDF export uses typst. Same correction
as
|
||
|
|
77929b73df |
docs(readme): Studio's PDF export needs LaTeX; typst only covers plain pandoc
The README said /studio-pdf and studio_export_pdf use typst (since v1.4.0) and that "PDF export from Studio/pandoc works out of the box" (since v1.9.1). Only the pandoc half is true: pi-studio uses $PANDOC_PDF_ENGINE, falling back to xelatex, and the image has never set it, so Studio export fails with "xelatex not found" (seen at v1.6.3 and v1.11.2). New "PDF export from Studio needs LaTeX" section: - do not set PANDOC_PDF_ENGINE=typst: pi-studio's LaTeX header is then printed as page-1 text while pandoc exits 0 - texlive-xetex alone is not enough (lmodern.sty, ulem.sty, pzdr font); texlive-xetex lmodern texlive-plain-generic texlive-fonts-recommended works, 37 packages / 491 MB, measured on v1.11.2 + pi-studio 0.10.1 by exporting and rendering the page AGENTS.md and Dockerfile.base comments carry the same claim; not changed. |
||
|
|
10a8030620 |
changelog(unreleased): pi-extensions 0d8e417, AWS SSO keeper times in 24-hour format
The keeper's status line, /aws-sso status, "session ended at" notice and "last login" line used toLocaleTimeString()/toLocaleString(); Dockerfile.base sets LC_ALL=en_US.UTF-8, so the TUI read "AWS ✓ since 01:25 PM". 0d8e417 prints HH:MM / YYYY-MM-DD HH:MM:SS whatever the locale. Devices overriding the locale to sv_SE in .env already saw 24-hour times. Measured in a v1.11.2 container: Node gives "01:25 PM" under en_US.UTF-8 and C.UTF-8, "13:25" under sv_SE.UTF-8; upstream tests at 0d8e417 pass 18/18 under en_US.UTF-8. Names the SHA check-doc-drift.sh check 9 wanted, so doc-drift is green again (rc 0 locally; red on |
||
|
|
a8f5b35d2a |
docs: correct the "pi-studio has no bind flag" claim everywhere it was baked
|
||
|
|
1e4c238a83 |
docs(readme): pi-studio has --listen-all; the remote recipe describes the 0.10 URL
README "Using pi-studio" said pi-studio hard-binds 127.0.0.1 with no bind flag.
It has had /studio --listen-all (binds 0.0.0.0) since 0.9.60; listed now as path
C, with a warning not to combine it with the studio-expose bridge on one port:
with STUDIO_EXPOSE=1, `/studio --listen-all --port 8765` fails with
"listen EADDRINUSE: address already in use 0.0.0.0:8765" (measured, tor-ms22,
v1.11.2).
The remote recipe described a 0.9 URL. Since 0.10 the printed URL also carries
studioTabState + hostedWorkspace and a token-only URL is answered 409; the
recipe now says to open the whole URL, names the wrapped-URL copy hazard
("This link looks damaged..."), says Studio must run on the published port
(only the laptop's side of ssh -L is free) and that --port does not move a
running server, adds --watch <file>, and gives an on-host curl that tells
"chain up" (403) from "Studio down or on another port" (empty reply).
All of it measured on tor-ms22 2026-10-14 (pi-studio 0.10.1): tunnel through
the host sshd + headless browser load, both modes; --listen-all reached on the
container's eth0 (200 / 403 without token). Docs only; no pin changes.
|
||
|
|
bf265f6d54 |
release(v1.11.2): smoke esbuild check present-iff-declared; pi-studio 0.10.1
Lint / hadolint (push) Successful in 8s
Lint / skill-floor (push) Successful in 13s
Lint / doc-drift (push) Successful in 17s
Lint / actionlint (push) Successful in 30s
Publish Docker Image / lint-gate (push) Successful in 26s
Publish Docker Image / resolve-versions (push) Successful in 16s
Publish Docker Image / base-decide (push) Successful in 15s
Publish Docker Image / build-base (push) Has been skipped
Publish Docker Image / smoke (push) Successful in 5m25s
Publish Docker Image / smoke-studio (push) Successful in 8m40s
Publish Docker Image / build-variant (push) Successful in 16m48s
Publish Docker Image / update-description (push) Successful in 8s
Publish Docker Image / promote-base-latest (push) Successful in 9s
Publish Docker Image / build-variant-studio (push) Successful in 21m23s
v1.11.1 (run 724) was tagged and never published: the smoke assertion "esbuild works at every install site" required esbuild to exist, and pi 1.2.0 dropped @earendil-works/chord, its only source. The image was right. - smoke-test.sh: esbuild must be present iff an installed package root or /opt repo root declares it, and compile at every site. Body tested 8 ways (v1.11.0-studio ok; sites removed / natives pruned / residue / bare declarer fail; fixture decoy ignored; v1.11.2 candidates ok). Full smoke on local arm64 candidates with run 724's args: old script 102/1 (CI reproduced), new core 103/0, studio 106/0 (0.9.63 and 0.10.1). - pi-studio v0.9.63 -> v0.10.1 (tagged 2026-10-14; CI takes the newest tag): devbox contract unchanged (manifest, client bundle, 11 tools, 6 commands, --no-browser/--port/--listen-all, env vars); exercised under pi 1.2.0 in headless Chromium (render, Run Prompt via ERDC, Files -> Document, port freed); in-image TUI starts with no extension issues. - CHANGELOG v1.11.1 -> v1.11.2; Dockerfile.variant/workflow cycle notes. No base input changed: base-6f3b20266de1 (built by run 724) is reused. |
||
|
|
4efa84bb42 |
release(v1.11.1): pi 1.2.0; CI builds the pinned pi-obsmem SHA
Lint / hadolint (push) Successful in 9s
Lint / skill-floor (push) Successful in 10s
Lint / doc-drift (push) Successful in 13s
Lint / actionlint (push) Successful in 29s
Publish Docker Image / lint-gate (push) Successful in 25s
Publish Docker Image / resolve-versions (push) Successful in 20s
Publish Docker Image / base-decide (push) Successful in 14s
Publish Docker Image / build-base (push) Successful in 42m31s
Publish Docker Image / smoke (push) Failing after 5m30s
Publish Docker Image / build-variant (push) Has been skipped
Publish Docker Image / update-description (push) Has been skipped
Publish Docker Image / promote-base-latest (push) Has been skipped
Publish Docker Image / smoke-studio (push) Failing after 8m38s
Publish Docker Image / build-variant-studio (push) Has been skipped
- pi 1.1.0 -> 1.2.0, audited on tor-ms22 from an isolated prefix: none of the four breaking changes reaches the image (3 before_agent_start handlers checked, no triggerTurn user, pi-studio themes load); agent-core dist byte-identical; turn cap N=19 honoured; atelier paints in both renderers; obsmem /om:* registered; real-config startup clean. - resolve-versions reads ARG PI_OBSMEM_REF from Dockerfile.variant instead of resolving obsmem master (which overrode the pin as --build-arg), requires a 40-char SHA, and warns when master moves past the pin. Block tested in isolation: real pin, short/branch/missing pin, older pin, GitHub unreachable. - CHANGELOG: Unreleased -> v1.11.1 — 2026-10-14. README pin table, BASE_REBUILD_DATE marker (base rebuilds anyway), stale VENDORED.md pointer. |
||
|
|
fa38e3e102 |
CHANGELOG: pi -p skips the palace wake-up and mailbox poll (mempalace-toolkit d5a03f4)
Measured on tor-ms22: 3 palace requests and 1.6 s, against 15 requests (12 tools/call) with MEMPALACE_WAKEUP_PRINT=1. check-doc-drift.sh: DRIFT before this entry, rc=0 after. |
||
|
|
1d53e008d4 |
CHANGELOG: pi -p skips the palace feed in print/JSON mode (mempalace-toolkit f590a3a)
Measured on tor-ms22: pi -p 2.9 s, --mode json 3.8 s (45 s with only the timer fix 2f0edf5, 315 s before it). MEMPALACE_FEED_PRINT=1 restores the feed. check-doc-drift.sh: DRIFT before this entry, rc=0 after. |
||
|
|
28e324d68f |
CHANGELOG: pi -p no longer idles 5 min after answering (mempalace-toolkit 2f0edf5)
The bridge's feed raced its mine against a setTimeout it never cleared, and print mode waits for an empty event loop. Measured on tor-ms22: exit at 315 s before the fix, 45 s after (the catch-up feed by design), 3.8 s with MEMPALACE_FEED=0. check-doc-drift.sh: rc=0, and rc=1 with the SHA removed. |
||
|
|
3da7312166 |
MCP via pi's built-in support: mcp-loader retired upstream; skill floor, docs, sanity check
pi-extensions 750b2a8 retired mcp-loader.ts in favour of builtin:mcp (an extension registering /mcp replaced the built-in and printed a warning on every start). This repo has no pin to move, since PI_EXTENSIONS_REF and MEMPALACE_TOOLKIT_REF float on main, so this commit carries what the next build changes: - CHANGELOG Unreleased: component table (pi-extensions a88fa99 -> 750b2a8, mempalace-toolkit 975ab92 -> a30af61), the switch and its measurements, the stale-link pruning, the new sanity check, and the floor refresh. check-doc-drift.sh rc=0. A two-sided check: with 750b2a8 blanked out of the CHANGELOG it fails rc=1, naming pi-extensions a88fa99 -> 750b2a8. - rootfs pi-extensions skill floor refreshed to 750b2a8. check-skill-floor.sh was rc=1 before and is rc=0 after. Base hash changes, so the next tag rebuilds the base. - recreate-sanity-check.sh: "no dangling extension links" (WARN). The existing count includes dangling links because ls lists them. Tested two-sided in a temp HOME (PASS clean; WARN naming mcp-loader.ts and x.ts.off). On tor-ms22 v1.11.0: rc=0, 36 pass / 3 warn / 0 fail (was 35/3/0). - DOCKER_HUB.md: pi-extensions list was three releases stale; it now lists the 10 that ship, plus an MCP line. Dockerfile.variant comment updated. - lint-shell.sh: 19 files clean at severity error. |
||
|
|
4ee94da523 |
release(v1.11.0): pi 1.1.0, pi-obsmem 022da5e, pi-atelier v0.15.0, all floating refs at newest
Lint / hadolint (push) Successful in 10s
Lint / skill-floor (push) Successful in 11s
Lint / actionlint (push) Successful in 19s
Lint / doc-drift (push) Successful in 22s
Publish Docker Image / lint-gate (push) Successful in 23s
Publish Docker Image / resolve-versions (push) Successful in 17s
Publish Docker Image / base-decide (push) Successful in 11s
Publish Docker Image / build-base (push) Successful in 50m31s
Publish Docker Image / smoke (push) Successful in 5m26s
Publish Docker Image / smoke-studio (push) Successful in 11m35s
Publish Docker Image / build-variant (push) Successful in 17m18s
Publish Docker Image / update-description (push) Successful in 9s
Publish Docker Image / promote-base-latest (push) Successful in 8s
Publish Docker Image / build-variant-studio (push) Successful in 20m28s
- ARG PI_VERSION 1.0.0 -> 1.1.0. Audited against the npm tarballs: the only
breaking change in 1.0.1..1.1.0 is 1.0.3's azure-openai-responses -> azure
rename, which nothing here uses. pi-agent-core's finishTurn handling is
byte-identical between 1.0.0 and 1.1.0.
- ARG PI_OBSMEM_REF 731c3d4 -> 022da5e (= master): #81 compaction keeps
unobserved context, #93 prebuilt dist/index.ts. src/agents/ unchanged.
- ARG PI_ATELIER_REF/VERSION v0.13.0 -> v0.15.0. Sidebar Auto/Manual modes,
Manual by default. README's sidebar section updated.
- SKILLSET_SNAPSHOT_REF e9e45f7 -> 96b840e via vendor-mempalace-skill.sh.
The vendored bytes are unchanged.
- Dockerfile.base BASE_REBUILD_DATE marker refreshed, at no cost, because the
base rebuilds anyway (rootfs + entrypoint-user.sh changed in
|
||
|
|
3f46f5fd29 |
changelog(unreleased): pi-task fallback covers the task children the AWS SSO keeper leaves alone
The keeper (pi-extensions aws-sso.ts, entry by pi@mbp-m1-2020 in
|
||
|
|
09ce100c92 |
changelog(unreleased): secret-guard (pi-extensions a88fa99) and pi-task model fallback (pi-toolkit 3760d90)
Both arrive with the next build without a pin change, because CI resolves pi-extensions and pi-toolkit main at build time. The components table now names them: pi-extensions 143a214 -> a88fa99, and a new pi-toolkit row 9c87ee8 -> 3760d90. Both ids were checked against ls-remote, and the baked values against /etc/pi-devbox/build-manifest.json on a v1.10.2 device. |
||
|
|
11e512e497 |
feat(entrypoint): link ~/.pi/agent/models.json to a mounted shared file
pi reads custom providers from ~/.pi/agent/models.json, and ~/.pi is the devbox-pi-config named volume. A file baked into the image would only seed a fresh volume, once. A hand-made link is one more step on every new volume: on 2026-10-12 that was the same `ln -s /opt/fleet-pi/models.json …` once per device, for one fleet-shared file. New rootfs/usr/local/lib/pi-devbox/link-pi-models.sh, called from entrypoint-user.sh after the pi-toolkit and pi-extensions installs. When /opt/fleet-pi/models.json exists (PI_MODELS_JSON overrides the path, =off disables it), it links ~/.pi/agent/models.json to it, honouring PI_CODING_AGENT_DIR. It only ever creates a link where nothing exists: a real file or a link of the user's own is left alone, with one NOTE line. With nothing mounted, it does nothing and prints nothing. A link whose target later vanishes is harmless; measured with pi 1.0.0, a dangling models.json behaves like a missing one (rc 0, no error, no custom models). Tests: - scripts/test-link-pi-models.sh: 13 two-sided cases against a throwaway HOME. Wired into lint.yml, so it runs on every push and not only at release. - Of 11 mutations, 10 are caught. The 11th (ln -s -> ln -sfn on the final link) is unreachable behind the guards. - smoke-test.sh: with nothing mounted no link appears, and the script links a mounted file. Both commands were run under dash here. - recreate-sanity-check.sh: pass, warn or fail on the link, but only when a shared file is mounted. Here it passes. Docs: - README "Custom model providers (models.json)": mount the DIRECTORY (git pull renames, and a single-file mount then shows "No such file" until recreate), keep keys in .env as "$NAME". - .env.example: PI_MODELS_JSON. Local gates green: lint-shell, check-workflow-shell, check-dockerfile-directives, check-base-hash, check-doc-drift. |
||
|
|
eb70928b16 |
changelog(unreleased): AWS SSO keeper (pi-extensions f885199); pi-studio v0.9.63 named
pi-extensions 143a214 -> f885199 adds aws-sso.ts: renews the Bedrock SSO session just after IdC ends it (end read from AWS's token cap), local mode via the host browser, remote mode via device code + ntfy push. Measured on mbp-m1-2020 2026-10-11/12. pi-studio moved upstream 641aa32 -> fae3ad3 (v0.9.63) and CI's newest-tag resolution will bake it; named so check-doc-drift passes, not yet reviewed. |
||
|
|
56e3742a2d |
fix(ci): skip the BuildKit check whose warning embedded this Dockerfile in CI output
Lint / hadolint (push) Successful in 10s
Lint / skill-floor (push) Failing after 13s
Lint / doc-drift (push) Successful in 18s
Lint / actionlint (push) Successful in 33s
Publish Docker Image / lint-gate (push) Successful in 32s
Publish Docker Image / resolve-versions (push) Successful in 17s
Publish Docker Image / base-decide (push) Successful in 13s
Publish Docker Image / build-base (push) Has been skipped
Publish Docker Image / smoke-studio (push) Successful in 5m38s
Publish Docker Image / smoke (push) Successful in 8m20s
Publish Docker Image / build-variant-studio (push) Successful in 20m34s
Publish Docker Image / build-variant (push) Successful in 21m5s
Publish Docker Image / update-description (push) Successful in 8s
Publish Docker Image / promote-base-latest (push) Successful in 9s
v1.10.1 (run 707) failed with ZERO failing steps: every step Success, `smoke` printing "101 passed, 0 failed", `smoke-studio` "104 passed, 0 failed", both jobs red. The clipboard fix from |
||
|
|
c2c42c34a2 |
docs(v1.10.1): cut v1.10.1; v1.10.0 stays tagged-but-never-published
Lint / skill-floor (push) Successful in 11s
Lint / doc-drift (push) Successful in 12s
Lint / hadolint (push) Successful in 14s
Lint / actionlint (push) Successful in 19s
Publish Docker Image / resolve-versions (push) Successful in 12s
Publish Docker Image / base-decide (push) Successful in 13s
Publish Docker Image / build-base (push) Has been skipped
Publish Docker Image / lint-gate (push) Successful in 25s
Publish Docker Image / smoke-studio (push) Failing after 5m43s
Publish Docker Image / build-variant-studio (push) Has been skipped
Publish Docker Image / smoke (push) Failing after 8m21s
Publish Docker Image / build-variant (push) Has been skipped
Publish Docker Image / promote-base-latest (push) Has been skipped
Publish Docker Image / update-description (push) Has been skipped
v1.10.0's build failed one assertion of 104 and published nothing. Rather than re-point a tag CI had already consumed, the number moves: a version that failed its build should stay failed and readable, not be quietly overwritten with different bytes. CHANGELOG gains a v1.10.1 section carrying the smoke fix (moved out of v1.10.0's Fixed list), and states plainly that everything under v1.10.0 ships here so the big section stays the content record. v1.10.0's heading is marked "tagged, never published — superseded by v1.10.1" with a blockquote naming the commit ( |
||
|
|
f3b3748223 |
fix(smoke): derive the clipboard assertion from what pi declares
v1.10.0's first tag build (run 704) failed on ONE assertion out of 104, and published nothing. The assertion was wrong, not the image. scripts/smoke-test.sh required @mariozechner/clipboard to be installed and require()-able at every install site, failing hard when the family was absent. pi 0.86.0 (#9163) "Replaced the external native clipboard dependency with bundled asynchronous macOS, Windows, and X11 helpers while preserving platform command and OSC 52 fallbacks". Measured in the published tarballs rather than inferred from the changelog: pi 0.85.1 -> dependencies include @mariozechner/clipboard@0.3.9 pi 0.87.1 -> no @mariozechner/* at all pi 1.0.0 -> no @mariozechner/* at all So on a correct v1.10.0 image the package is legitimately gone, `if [ -z "$sites" ]; then exit 1` fired, and both smoke jobs went red on the same line: smoke 100 passed/1 failed, smoke-studio 103 passed/1 failed with every studio-specific assertion green. build-variant, build-variant-studio, promote-base-latest and update-description all skipped -> nothing published. The gate worked; it was enforcing pi 0.85.1's dependency graph. The guard is NOT deleted, because its "fail when absent" shape is the thing that stops prune_foreign_natives() from deleting the binding instead of the surplus platform copies. It now derives the expectation from what the image's pi actually DECLARES: declares + install loads -> pass declares + no install -> FAIL (prune removed function) does not declare + no install-> pass (pi bundles it since 0.86.0) does not declare + install -> FAIL (orphan nothing depends on) That re-arms automatically if a future pi re-adds the dependency, which a skip-if-absent would not. Verified as that four-quadrant truth table on EXIT CODES, not messages, since run() keys on status: rc=0/1/0/1 as listed. The final check extracted the command string verbatim from the committed file and ran it through `sh -c` the way run() does, so the escaping was tested as shipped rather than as drafted. Gates: doc-drift 23 OK / 0 DRIFT; base-hash, workflow-shell, skill-floor, lint-shell all rc=0. smoke-test.sh is not a base-hash input, so the base layer does not rebuild. |
||
|
|
12f99c49e3 |
docs(v1.10.0): adopt pi's fullscreen default and document the way back
Lint / skill-floor (push) Successful in 7s
Lint / hadolint (push) Successful in 15s
Lint / actionlint (push) Successful in 17s
Lint / doc-drift (push) Successful in 14s
Publish Docker Image / lint-gate (push) Successful in 21s
Publish Docker Image / resolve-versions (push) Successful in 10s
Publish Docker Image / base-decide (push) Successful in 9s
Publish Docker Image / build-base (push) Successful in 55m32s
Publish Docker Image / smoke (push) Failing after 8m50s
Publish Docker Image / build-variant (push) Has been skipped
Publish Docker Image / promote-base-latest (push) Has been skipped
Publish Docker Image / update-description (push) Has been skipped
Publish Docker Image / smoke-studio (push) Failing after 9m55s
Publish Docker Image / build-variant-studio (push) Has been skipped
Decision: keep pi 1.0.0's default (tuiMode "fullscreen"). No tuiMode is baked,
so the image follows upstream rather than pinning the fleet to either mode -
but "we inherited a changed default" is only acceptable if the revert is
written down, so it now is.
README gains a "Terminal UI mode" section ahead of the pi-atelier section,
because the two interact. It gives all three scopes, taken from pi 1.0.0's own
docs/settings.md and docs/cli.md rather than from the changelog prose:
- permanent: "tuiMode": "regular" in ~/.pi/agent/settings.json
- one session: pi --tui-mode regular (the flag is real: cli.md:221)
- one project: the same key in .pi/settings.json, which overrides the agent
directory
It also documents the four related settings (fullscreenExitOutput,
fullscreenScrollbar, fullscreenCopyOnSelect, fullscreenWheelScrollLines) and
two things specific to this image:
- tmux: fullscreen uses the alternate screen, so tmux copy-mode shows the
pane history AROUND pi, not pi's transcript. That is the concrete reason
someone here would want "regular" back.
- fullscreenWheelScrollLines "auto" behaves differently over SSH (caps fast
wheel spins at 6 lines/event) than in a local macOS terminal (1 line) -
worth naming because this container is normally driven over SSH.
- pi-atelier works in BOTH modes (upstream has handled regular vs fullscreen
renderers separately since pi 0.84), so reverting costs nothing. Stated so
nobody assumes the sidebar is the price of the old scrollback.
The python3 merge snippet in that section was RUN before being documented:
against a realistic settings.json under an overridden HOME, twice, confirming
it is idempotent and preserves sibling keys including nested objects and the
_comment fields the seeded file uses. Documented code that has never been
executed is a guess.
DOCKER_HUB.md gets a short version of the same note, because CI reads it from
the TAG and POSTs it to Docker Hub as full_description - a behaviour change
this visible should not require reading the repo to undo.
Gates: doc-drift 23 OK / 0 DRIFT / 0 SKIP / 0 FAIL; base-hash, workflow-shell,
skill-floor, lint-shell all rc=0.
|
||
|
|
e026f6f65e |
release(v1.10.0): pi 1.0.0 and pi-atelier v0.13.0, on measured evidence
Renames the unpublished v1.9.5 section to v1.10.0 and adopts the two bumps
that section had deliberately deferred. v1.9.5 was never tagged or published,
so nothing shipped under that name.
A minor, not a patch. v1.9.5 was numbered under the "patch - pi version bumps"
rule, but this release carries a pi MAJOR, a three-minor pi-atelier jump, four
new base packages and a new mailbox feature. The release carrying a 1.0.0
should not be the one numbered as a patch.
pi 0.85.1 -> 1.0.0. v1.9.5 held at 0.87.1 because obsmem's only compatibility
work names Pi 0.87 and no upstream issue mentions 0.99 or 1.0. That reasoning
had a hole worth naming: "no issue mentions 1.0" is an ABSENCE OF A STATEMENT,
not a measurement. So 1.0.0 was measured, against the published npm tarballs
for 0.87.1 and 1.0.0 unpacked side by side:
- 1.0.0 has NO "### Breaking Changes" section at all. Those belong to 0.87.0,
0.86.0, 0.84.3, 0.84.0, 0.83.0, 0.80.8, 0.80.7, 0.75.0 - none to anything
between 0.88 and 1.0.0. The major is a milestone (fullscreen default,
leaner codemode), not an API break.
- finishTurn is still in 1.0.0's dist, so the pinned obsmem SHA keeps the API
it migrated to.
- All 8 pi.* APIs our extensions call exist in 1.0.0's dist: registerTool,
registerCommand, registerFlag, getFlag, on, exec, sendMessage,
sendUserMessage.
- engines.node >=22.19.0 on both; the image ships 24.x.
The 0.99.2 change that looked fatal and is not: from 0.99.2 the DEFAULT MCP
exposure is `codemode`, so such tools are "neither declared to the model nor
listed" and must be found with searchTools(). That would gut the MemPalace
protocol if MemPalace were a builtin-MCP server. It is not - mempalace.ts and
mcp-loader.ts each run their own MCP client and register tools via
pi.registerTool(), which is why they are named mempalace_search and not
mcp__mempalace__search. Second route: settings.json has neither an mcpServers
block nor an mcp block. Filed upstream under "Changed", not "Breaking", so it
would have been easy to meet the hard way.
pi-atelier v0.10.3 (ed3837b) -> v0.13.0 (34d26f1), closing the gap v1.9.5
flagged as the pi bump's residual risk. peerDependencies are unchanged at pi
>=0.84.0 across v0.10.3/v0.12.1/v0.13.0 - a FLOOR, so not evidence of
anything. What was checked instead: v0.13.0 carries its OWN breaking change
(the entry point no longer exports the internal registry and layout helpers),
which reaches nothing of ours - grepping pi-atelier and registerSidebarPanel
across mempalace-toolkit, pi-devbox, pi-extensions, pi-fork,
pi-observational-memory and pi-studio returns ZERO matches in all six. v0.12.0
removed the showSessionActions setting (0 references here) and moved the
Control Center shortcut Alt+A -> F6 (0 references here, so no doc goes stale).
showSidebarAgent/showSidebarTodos, the only atelier keys README names, are
both still in v0.13.0's README. TuiMainScreen and renderLayoutFrame both still
exist in pi 1.0.0's dist.
PI_ATELIER_VERSION is bumped with PI_ATELIER_REF; it is a separate ARG and the
image label would otherwise have lied.
v0.13.0 is an ANNOTATED tag: `ls-remote --tags` reports the tag object
(dd06971), not the commit (34d26f1). check-doc-drift.sh resolves the commit, so
that is what the CHANGELOG names. The gate caught the wrong SHA on the first
attempt - noted in the CHANGELOG for future bumps.
NOT PROVEN, and stated so acceptance does not mistake it for cleared: grepping
dist shows the SYMBOLS survive, not that their SIGNATURES are unchanged -
necessary, not sufficient. 1.0.0 is four days old and neither obsmem nor
atelier has a commit naming it. Acceptance must prove the obsmem workers CAP
TURNS (peerDeps are *, so a mismatch is silent) and that the atelier sidebar
PAINTS.
User-visible behaviour change, deliberately NOT overridden: 1.0.0 makes the TUI
fullscreen by default, replacing the terminal's normal scrollback.
tuiMode: "regular" restores the old behaviour. No baked default is set, so the
image inherits upstream's choice rather than silently pinning the fleet.
Gates: check-doc-drift 23 OK / 0 DRIFT / 0 SKIP / 0 FAIL; base-hash,
workflow-shell, skill-floor, lint-shell all rc=0.
|
||
|
|
9d0b3dec0b |
release(v1.9.5): pi 0.87.1 with pi-obsmem pinned to the merged finishTurn fix
v1.9.4 held pi at 0.85.1 because 0.87.0 REMOVED `shouldStopAfterTurn`, which
pi-observational-memory 3.1.4 still used in all three workers. Its peerDeps are
`*`, so nothing refuses at install time and the breakage is silent at runtime --
turn caps ignored, workers losing their specialised prompts. PR #83 fixes that
and merged 2026-09-23.
Re-measured 2026-10-01 across src/agents/{observer,reflector,dropper}/agent.ts,
deliberately reusing the v1.9.4 audit's counting method so the numbers compare:
e7d77dc (3.1.4, baked in v1.9.4): shouldStopAfterTurn 3, finishTurn 0, systemPrompt 3
731c3d4 (pinned here) : shouldStopAfterTurn 0, finishTurn 3, systemPrompt 0
All three workers migrated, and the 0.86.0 AgentContext.systemPrompt reads are
gone too. The coupling is asymmetric and that is why these move in ONE commit:
3.1.4 + 0.87.1 silently ignores turn caps, and 731c3d4 + 0.85.1 breaks the
workers outright, because finishTurn does not exist before 0.87.0.
Pinned to a SHA rather than waiting for a tag, departing from the v1.9.4
instruction to wait for a release: #83 is merged but the newest obsmem tag is
still 3.1.4, cut 2026-09-20, BEFORE the merge. Upstream tags slowly and moves
master often (e7d77dc -> 1529e14 -> 731c3d4 in nine days), so waiting means
holding pi indefinitely. A pinned SHA keeps the property `master` lacks:
rebuilding this tag later produces the same image.
The 40-char form is load-bearing, not pedantry. check-doc-drift.sh recognises a
literal SHA only through a 40-char match, so a 7-char pin would fall through to
its branch-or-tag lookup, fail to resolve, and downgrade pi-obsmem's drift check
to a silent SKIP -- a pin that reads correctly and is no longer verified. Full
gate run after this change: 23 OK, 0 DRIFT, 0 SKIP, 0 FAIL.
0.99.0/0.99.1/0.99.2 and 1.0.0 all exist upstream and are deliberately skipped:
obsmem's only compatibility work names Pi 0.87 (2b1dc1c) and a repo-wide
issue/PR search for 0.99 or 1.0 returns zero matches. 1.0.0 is its own round.
pi-atelier stays at v0.10.3 and is the residual risk. Its peerDeps declare pi
>=0.84.0 -- a floor, satisfied -- on both v0.10.3 and the current v0.12.1, so it
spans this bump. But atelier hooks pi TUI internals that a declared floor does
not protect, and an under-declared floor is exactly what failed to warn anyone
at pi 0.84. Acceptance must confirm the sidebar PAINTS, using the two-sided
check from 0.84.4/0.85.1 that distinguishes "loaded" from "silently absent".
Also here:
- check-doc-drift.sh gains a pi-obsmem pin check. The ref-move check covers the
same component, but for a pinned SHA it can only answer "upstream did not
move", never "the table still says what we bake".
- scripts/lint-shell.sh mode 100644 -> 100755. Pre-existing since
|
||
|
|
cb8969ef2c |
changelog: adopt pi-studio v0.9.61 and mempalace-toolkit 975ab92
Both are floating refs that moved since v1.9.4, so the next tag would bake them either way; the gate's complaint was that nothing in the repo said so. Naming them is the fix the gate actually asks for. mempalace-toolkit 2167a1b -> 975ab92 adds mailbox dormancy: an ask may declare a `dormant_unless` predicate and is withheld from the ANNOUNCED owed-set while every condition still matches its baseline. It is backward compatible by construction -- an ask without the key behaves exactly as before -- and fail-visible: any predicate that cannot be evaluated announces the ask rather than hiding it, because the dangerous failure is work that disappears, not a spurious nag. pi-studio v0.9.60 -> v0.9.61 is adopted as-is. NOT pinned, deliberately, and the reason is worth recording: check-doc-drift.sh gives pi-studio kind `studio`, which resolves the highest semver TAG of the upstream repo and ignores PI_STUDIO_REF entirely (resolve_studio, ~line 551). Setting PI_STUDIO_REF=v0.9.61 would therefore not be tracked by the gate -- the moment upstream tags v0.9.62 the gate would report DRIFT against a value we no longer bake, i.e. a false positive by construction. Pinning pi-studio is a coherent thing to want, but it requires moving it from kind `studio` to kind `ref` in the gate at the same time, which changes release-gate semantics and is a separate decision from adopting this release. Still drifting, and left drifting on purpose: pi-obsmem e7d77dc -> 731c3d4. PI_OBSMEM_REF=master is CI-resolved at build time, master is 4 commits ahead of the 3.1.4 tag, and the newest tag remains 3.1.4 -- so PR #83 ("Fix Pi 0.87 memory worker compatibility", merged 2026-09-23) is still reachable only from master. Whether to bake an unreleased master or stay on 3.1.4 and hold pi is a pin decision with a silent failure mode behind it (obsmem's peerDeps are all `*`), so it is not being made in a changelog commit. |
||
|
|
31f36182bf |
feat(base): add sqlite3, bc, dc and bsdextrautils
sqlite3 is the only one of the four that was genuinely missing rather than merely absent. MemPalace keeps both the palace and the logstream as SQLite files (palace/chroma.sqlite3, logstream.sqlite3) and mempalace_status reports a sqlite_integrity block, so every integrity or forensics check on this fleet has so far gone through a python3 -c one-liner because the CLI was not in the image. bc and dc are low value on their own and the CHANGELOG says so plainly: awk, python3 and perl are all already baked and each is strictly more capable. They are here because copy-pasted shell snippets assume bc exists. What actually went wrong on 2026-09-28 was NOT bc's absence: printf '%6.2f' was handed the empty output of the missing bc and rendered it as a confident "0.00 days" for a figure that was really 4.81 days. No package fixes that failure mode -- only not taking a formatted number on trust does. bsdextrautils ships /usr/bin/column. Cost MEASURED rather than estimated: 1311 KB total (587 + 236 + 149 + 339) and ZERO transitive packages. libsqlite3-0, libreadline8t64, zlib1g, libsmartcols1 and libtinfo6 each already report "install ok installed", so nothing new is pulled under --no-install-recommends. Package names were verified with apt-cache and dpkg -S on a real trixie host instead of being inferred, which caught two traps that would each have produced either a build failure or a silently missing binary: dc is a SEPARATE binary package from bc on Debian, and column ships in bsdextrautils, not in the pre-bullseye bsdmainutils where it used to live. Declined at the same time, recorded so the omission reads as a decision rather than an oversight: datamash and xsv/csvkit. Python's stdlib csv module handled a real 7-file Excel-export concatenation that day -- UTF-8 BOM, no trailing newlines, and bare CR/LF inside quoted fields -- correctly and without them. Dockerfile.base is in the base hash, so the next tag rebuilds the base (~64 min) regardless of what else it carries. |
||
|
|
2278b22ba7 |
fix(ssh): wire git core.sshCommand to the writable sidecar, and assert it
~/.ssh is commonly bind-mounted READ-ONLY from the host, so a per-host `ControlPath ~/.ssh/cm/%r@%h:%p` — the standard CGNAT multiplexing recipe, and correct on the host — resolves inside an unwritable dir in the container. Every push dies `unix_listener: cannot bind to path ...: Read-only file system`, behind git's misleading "make sure you have the correct access rights". setup-lan-access.sh already writes the fix: ~/.ssh-local/config overrides ControlPath into the writable ~/.ssh-local/cm BEFORE `Include ~/.ssh/config`, so -F repairs the socket path and keeps every per-host User/Port/IdentityFile. entrypoint-user.sh now points git at it, guarded on the sidecar existing — setup-lan-access.sh writes none on native Linux Docker, where -F at a missing file would break every git-over-ssh call instead of fixing one. An existing core.sshCommand is left alone (first-wins, as for the three git settings above). Why code and not another doc line: the remedy was already in the global AGENTS.md, in pi-devbox-environment SKILL.md §3, in 24 MemPalace drawers from three devices, and printed verbatim by recreate-sanity-check.sh — and an agent that had run that script two hours earlier still hit the failure and reinvented a /tmp/sshcm workaround. A fifth copy was not the missing piece. Assertions, each where it can actually pass: - smoke-test.sh: two STATIC greps (wiring line + its [ -r ] guard). `run` uses --entrypoint="", so asserting the runtime value there would repeat the v1.8.0 mistake of an assertion that cannot pass, unvalidated until the next tag. - smoke-test.sh runtime phase: a BICONDITIONAL — sidecar present => must route through it; absent => must be unset. The absent arm is the one CI exercises (native Linux runner), so "is set" would have failed CI for a correct image. - recreate-sanity-check.sh: the runtime assertion, plus an explicit fail for the inverted state (set while the sidecar is missing). The permanent "default ssh precedence" warning keeps its severity but now states that it is structural and can never reach zero, and whether git is wired, unwired, or has no sidecar. All five arms exercised against the real script before commit; that caught a defect in the first draft, which reported "git IS wired ... unaffected" about a state where the sidecar was gone and every git-over-ssh call failed. Host ~/.ssh/config needs no change: the same line is right on the host and unusable through a read-only mount, so the fix belongs in the container layer. |
||
|
|
37fcbfcf04 |
fix: three v1.9.4-acceptance findings (installer WARN, init sentinel, hash collation)
Unreleased; no pin moves except pi-extensions master 25c1265 -> 143a214. 1. entrypoint-user.sh: first-run sentinel is now config.json, which is what `mempalace init` writes. The old test on palace/ (created by mining, not init) re-fired "Initializing MemPalace" on every boot of a container that never mined locally: v1.9.4 acceptance measured 1 line after one boot, 2 after a restart. Idempotent, so harmless; the comment lied. This file is a base-hash input, so the next tag rebuilds the base (326fb7c03949 predicted with the pinned sort below; f40c4b7b103d before). 2. Dockerfile.variant: `git config --system --add safe.directory` for the seven root-owned /opt clones (listed, not `*`). Since git 2.35.2 any git command in a repo owned by another user fails "dubious ownership"; as `developer` that made `git -C /opt/pi-atelier rev-parse` print nothing (one false FAIL in the v1.9.4 acceptance) and is the root cause of the per-boot "WARN: pi-extensions install.sh failed" (fixed at the source in pi-extensions 143a214; this is the belt to that suspender). Verified live in a v1.9.3 container: add -> rev-parse prints 25c1265, unset -> fatal again; /etc/gitconfig restored to its 5 lines afterwards. 3. docker-publish.yml: `find -print0 | LC_ALL=C sort -z` in base-decide. sort collates per locale; identical rootfs hashed to base-f40c4b7b103d under C/C.UTF-8 (== run 695) and base-d8df62216a81 under sv_SE/en_US.UTF-8. The runner exports LANG=C.UTF-8, so the hash was stable by accident. Pin is hash-neutral: pinned sort + HEAD inputs reproduces f40c4b7b103d exactly. Per-command prefix only; nobody's locale changes. CHANGELOG: new `## Unreleased` above v1.9.4 naming pi-extensions 143a214 (check 9 rc=0), the three fixes, and the synlig 3.10.0 hub upgrade that v1.9.4 listed as still open. One invented URL org (gwpl) caught before commit; upstream is elpapi42, as Dockerfile.variant:151 says. Gates: check-doc-drift rc=0, check-base-hash rc=0, lint-shell rc=0 (16 files), hadolint 2.15.1 rc=0, YAML parses (10 jobs), bash -n rc=0. |
||
|
|
16fddebd43 |
docs(v1.9.4): the client/server skew is narrower than the release commit said
Lint / hadolint (push) Successful in 8s
Lint / skill-floor (push) Successful in 11s
Lint / actionlint (push) Successful in 17s
Lint / doc-drift (push) Successful in 17s
Publish Docker Image / lint-gate (push) Successful in 21s
Publish Docker Image / resolve-versions (push) Successful in 15s
Publish Docker Image / base-decide (push) Successful in 12s
Publish Docker Image / build-base (push) Successful in 1h4m42s
Publish Docker Image / smoke (push) Successful in 6m4s
Publish Docker Image / smoke-studio (push) Successful in 9m18s
Publish Docker Image / build-variant (push) Successful in 19m20s
Publish Docker Image / update-description (push) Successful in 7s
Publish Docker Image / promote-base-latest (push) Successful in 11s
Publish Docker Image / build-variant-studio (push) Successful in 24m15s
|
||
|
|
e3b38cdb0b |
release(v1.9.4): mempalace 3.10.0 with a pinned palace root, pi-atelier v0.10.3, pi held at 0.85.1
Three pins move or are deliberately held; the CHANGELOG's Unreleased section
becomes the v1.9.4 entry in this same commit, because since
|
||
|
|
0d324f1855 |
changelog: name pi-obsmem 3.1.4 (e7d77dc) as the next rebuild's implicit adoption
check-doc-drift.sh was rc=1: pi-observational-memory moved cba0334 (3.1.3) -> e7d77dc (3.1.4) upstream on 2026-09-20, the day after the v1.9.3 tag, and nothing named it above the v1.9.3 heading. No code change is needed or wanted here. PI_OBSMEM_REF defaults to master in Dockerfile.variant and CI's resolve-versions turns that into a SHA at build time, so the next rebuild adopts this whether or not anyone acts -- which is precisely why it has to be written down. There is no pin in this repo to bump, so the CHANGELOG is the only record a reader of the next tag would have. v1.9.3 itself has no defect: its manifest, image labels and baked /opt/pi-observational-memory clone all agree on cba0334. The drift is post-tag, so a green drift reading taken on 2026-09-19 was correct when taken. Measured, not assumed: the range cba0334...e7d77dc is 4 commits whose only substance is one line in src/agents/worker-stream.ts (preserve registry receiver when resolving stream, PR #78 / issue #77) plus a 19-line regression test; the rest is the version bump and release merge. peerDependencies are unchanged (all four @earendil-works/* peers still *) and there is no engines block, so no pi or node floor to clear. Gate now rc=0: "OK pi-obsmem cba0334 -> e7d77dc since v1.9.3, named above the v1.9.3 heading". |
||
|
|
6c13f43ac8 |
release: date the v1.9.3 section for the tag
Lint / skill-floor (push) Successful in 10s
Lint / hadolint (push) Successful in 12s
Lint / actionlint (push) Successful in 17s
Lint / doc-drift (push) Successful in 17s
Publish Docker Image / lint-gate (push) Successful in 16s
Publish Docker Image / resolve-versions (push) Successful in 10s
Publish Docker Image / base-decide (push) Successful in 9s
Publish Docker Image / build-base (push) Successful in 41m49s
Publish Docker Image / smoke (push) Successful in 8m45s
Publish Docker Image / smoke-studio (push) Successful in 9m8s
Publish Docker Image / build-variant (push) Successful in 17m42s
Publish Docker Image / update-description (push) Successful in 11s
Publish Docker Image / promote-base-latest (push) Successful in 26s
Publish Docker Image / build-variant-studio (push) Successful in 21m28s
CI checks out the tag, so the CHANGELOG heading has to name the version before the tag exists (same convention as v1.9.1/v1.9.2). check-doc-drift check 9 was sabotage-tested for exactly this rename and still passes: everything above the published v1.9.2 heading is the pending text. Contents of v1.9.3 (all measured, nothing inherited): - |
||
|
|
960aada769 |
changelog: dependency audit 2026-09-19 — mempalace 3.10.0 measured and deliberately deferred
Every row by direct command: npm/PyPI/GitHub release APIs, git ls-remote, the v1.9.2 config labels via the registry API, and binaries in a running v1.9.2 container for the floating tools. Only pin with a newer upstream is mempalace 3.9.0 -> 3.10.0, and it is NOT a small bump: new installs move to ~/.config/mempalace (entrypoint-user.sh tests ~/.mempalace/palace and entrypoint.sh persists ~/.mempalace, so a fresh container would initialise outside the volume), and mempalace_event_list flips to newest-first by default (the toolkit's deriveClosed passes no order, deriveOwed on 2 of 3 calls — server-default-dependent). Deferred to its own release with the three measured preconditions listed. |
||
|
|
b9057fdc8c |
Dockerfile.base: LABEL se.jordbo.pi-devbox.mempalace-version, inherited by both variants
Closes the blind spot check 9 ( |
||
|
|
cb6d9e5dd0 |
check-doc-drift: check 9 — what the next build bakes differently must be named in the CHANGELOG
The two most recent Unreleased entries (pi-extensions 25c1265: task tool +
fork-gate; mempalace-toolkit 817b3a8: the mine deadline that never reached
the transport) reached this image through floating *_REF=main ARGs. Neither
produced a diff here, so nothing asked for a CHANGELOG line, and neither had
one until a reader asked. Same class as check 8 — a claim with no in-repo
anchor rots — and the hand practice that covered it (the per-release
"Dependency audit" table, Baked-in vs Upstream-now) is a someone-remembers
mechanism that had lapsed.
Method, no docker/crane/token: last published vX.Y.Z from Hub's tag list
(one request, now shared with check 8); its amd64 config blob via the
anonymous registry API carries one se.jordbo.pi-devbox.<name>-ref label per
component with the SHA the build-args baked. "Would bake" is resolved the way
resolve-versions does it: 40-hex ARG is itself, branch/tag via git ls-remote
with the peeled ^{} form preferred, pi-studio = highest semver tag (label on
<tag>-studio), PI_VERSION as a literal against the pi-version label. Nine
components, ~7.5 s. Unchanged: OK. Moved: the new value's 7-char prefix (tag
for studio, version for pi) must appear above the last published version's
"## " heading — Unreleased plus any not-yet-published section, which is what
the release commit turns Unreleased into, so the tag build passes on the same
text. A published tag with no heading is a failure, not a skip.
First run found a move nobody had recorded: pi-observational-memory 7b397f4
-> cba0334 (6 commits, 3.1.1 -> 3.1.3; memory workers now routed by the
model's exact provider instead of the first registered provider with a
matching api — upstream #70). Named in the CHANGELOG with the honest scope:
no expected effect on the shipped bedrock-only configuration, unmeasured.
Sabotage-tested, expectation written before each run:
obsmem SHA removed from CHANGELOG ............ rc=1 DRIFT
Unreleased renamed to "## v1.9.3 — …" ........ rc=0 (release-commit shape)
"## v1.9.2" heading mangled to v1.9.2-typo .... rc=1 — this one FAILED first:
\b accepted "v1.9.2-typo" as v1.9.2's heading; now (\s|$)
bare "## v1.9.2" (no date) ................... rc=0
ARG PI_OBSMEM_REPO renamed ................... rc=2 (blind gate must not pass;
read_arg calls are bare top-level assignments on purpose — nested in a
heredoc's $(...) the exit 2 is swallowed by cat)
offline (proxy to a closed port) ............. rc=0, 2 SKIPs counted
SKIP_REF_CHECK=1 ............................. rc=0, 1 SKIP counted
restored ..................................... rc=0, files byte-identical
GIT_TERMINAL_PROMPT=0 on ls-remote: a repo flipped private fails in 0.3 s as
a SKIP instead of waiting for a username until the job times out.
Header, lint.yml job comment and AGENTS.md all still said this gate "needs
no network" — false since check 8 (2026-09-14). Now: two classes, hermetic
1-7 and published-state 8-9 that SKIP loudly and counted when offline.
Considered and not added: compose <-> .env.example variable cross-check (the
four mismatches are commented-out lines, the mempalace-server compose file's
own variables, and entrypoint-consumed ones — it would fire on nothing
wrong); "documented tag exists on Hub" (check 8 already SKIPs by name; a
hard fail would misreport the tag-to-publish window).
|
||
|
|
ea896054df |
changelog: task tool + fork-gate (pi-extensions 25c1265), and the mine deadline that never reached the transport (mempalace-toolkit 817b3a8)
Both land in the image through floating refs (PI_EXTENSIONS_REF=main,
MEMPALACE_TOOLKIT_REF=main), so neither shows up as a diff in this repo — the
changelog is the only place a reader of a pi-devbox tag learns that the
container's delegation and feed behaviour changed. Recorded under Unreleased
with the measurements: 5/5 real fork briefs redirected, live pi -p proof for
gate and tool, 2-of-6 → 6-of-6 on the toolkit's new timeout test.
Dockerfile.base: the "Stall protection" comment listed MEMPALACE_MCP_TIMEOUT_MS
as THE tool-call deadline; it now says the feed's mine carries its own.
Comment-only, but Dockerfile.base is hashed into base_tag; the rebuild was
already forced by
|
||
|
|
f25efa074d |
fix(sanity): verify the ControlPath ssh RESOLVES, not the dir the image creates
recreate-sanity-check.sh asserted `/tmp/sshcm exists with mode 700` and printed
a green tick while every ssh in the container died rc=255. It was right about
what it checked: the breakage was in the directory a CONFIG named, not the one
the image creates, and the old check could not see the disagreement between them.
Found on the v1.9.2 first boot on emb-7kj4vr4g. A durable ~/.pi/ssh/config,
hand-written into the ~/.pi named volume by the previous session so it would
survive the recreate, declared `ControlPath /tmp/ssh-cm/%C` — with a hyphen.
Nothing here creates that path; the canonical spelling is /tmp/sshcm, identical
in Dockerfile.base, entrypoint-user.sh, recreate-sanity-check.sh and
smoke-test.sh. ControlMaster auto with an unusable ControlPath does not degrade
to an unmultiplexed connection — it fails hard:
unix_listener: cannot bind to path /tmp/ssh-cm/<hash>: No such file or directory
rc=255, remote command never runs.
Now: resolve the ControlPath ssh itself would use via `ssh -G` and require its
parent to exist and be writable. -G applies real config precedence (first-value-
wins, the system drop-in, Include, an -F override), so it answers "which rule
captured this host" instead of re-implementing the guess, and it never opens a
connection — 0.116s for 48 hosts.
This also puts a check under a caveat documented in prose in Dockerfile.base
("SSH client defaults") and verified nowhere: a per-host ControlPath under a
read-only bind-mounted ~/.ssh gives the identical failure, "Read-only file
system". On the machine this was built on that is 16 of 50 hosts — freeipa-1..6,
gitea.egl.lan, runner-1..3, tor-ms22 — never reported by anything before.
Severity split is deliberate. Default ssh precedence legitimately lands in the
read-only ~/.ssh for any host whose own config pins it there, and the supported
workaround (ssh -F ~/.ssh-local/config, generated every start by
setup-lan-access.sh) already exists, so that is a warn. Failing it would paint
the script red on every run of every device, and a check that fires benignly
every time is one you learn to ignore — the same reasoning that keeps
lint-shell.sh at -S error. The sidecar route is prescribed, so there it is a
hard fail. Host lists cap at six names plus a count: unreadable output is
ignored output.
Teeth proven both directions, each sabotage confirmed by diff BEFORE the result
was believed: ControlPath -> nonexistent dir => rc=1; -> existing-but-read-only
dir => rc=1; restored => rc=0 with the sidecar byte-identical.
No config was added to fix the original problem — the fix was to DELETE
~/.pi/ssh/config, a third hand-maintained copy of what setup-lan-access.sh
already generates from version control on every container start, with fewer
features and one typo. The host-owned ~/.ssh/config is left alone on purpose:
those ~/.ssh/cm paths are correct on the host, where ~/.ssh is writable.
Also lint-shell.sh: the SC2088 count in the severity-choice rationale said 19
and is now 20, with the reproduce command recorded. That command needs a `$ `
prefix — a comment whose first word is "shellcheck" is parsed as a directive,
and the malformed one tripped SC1072/SC1073 at severity error. The gate caught
it on the very commit that introduced it.
|