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.
This commit is contained in:
@@ -108,7 +108,14 @@ jobs:
|
||||
id: compute
|
||||
run: |
|
||||
# Hash inputs that determine the base image's contents.
|
||||
# Order is fixed via `find -print0 | sort -z` for reproducibility.
|
||||
# Order is fixed via `find -print0 | LC_ALL=C sort -z`. The LC_ALL=C is
|
||||
# load-bearing: sort collates per locale, and a dictionary locale
|
||||
# (sv_SE/en_US.UTF-8) orders rootfs differently from byte order, giving
|
||||
# a different hash for identical content (measured 2026-09-22:
|
||||
# base-f40c4b7b103d under C/C.UTF-8 vs base-d8df62216a81 under
|
||||
# sv_SE.UTF-8). The runner ships LANG=C.UTF-8 today, so this pin
|
||||
# changes nothing now; it stops the hash depending on that accident.
|
||||
# Predicting base_tag locally MUST use the same prefix on this sort.
|
||||
# Junk filters: __pycache__/*.pyc and macOS metadata are gitignored
|
||||
# locally but still picked up by `find rootfs -type f` on a clean CI
|
||||
# checkout. Exclude them defensively.
|
||||
@@ -120,7 +127,7 @@ jobs:
|
||||
! -name '*.pyc' \
|
||||
! -name '.DS_Store' \
|
||||
! -name '._*' \
|
||||
-print0 2>/dev/null | sort -z | xargs -0 cat 2>/dev/null
|
||||
-print0 2>/dev/null | LC_ALL=C sort -z | xargs -0 cat 2>/dev/null
|
||||
cat entrypoint.sh entrypoint-user.sh
|
||||
# mempalace-toolkit is cloned in Dockerfile.base at a ref CI
|
||||
# resolves to a SHA; fold it in so base_tag changes when the
|
||||
|
||||
@@ -11,6 +11,83 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
||||
|
||||
---
|
||||
|
||||
## Unreleased
|
||||
|
||||
Three small fixes found by the v1.9.4 first-boot acceptance and the CI base
|
||||
hash prediction, none release-worthy on its own. Nothing here changes a pin.
|
||||
`entrypoint-user.sh` is in the base hash, so the next tag rebuilds the base
|
||||
(~64 min) regardless of what else it carries.
|
||||
|
||||
### Components that move with the next build
|
||||
|
||||
| Component | Baked in v1.9.4 | Next build | Why |
|
||||
|---|---|---|---|
|
||||
| pi-extensions | `25c1265` | **`143a214`** | `install.sh`: skip hook activation on a clone this user cannot configure (below) |
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Boot log: `WARN: pi-extensions install.sh failed (continuing)` on every start
|
||||
of every device** — pi-extensions `143a214`. `/opt/pi-extensions` is root-owned
|
||||
and `install.sh` runs as `developer`; git refuses the repo ("dubious
|
||||
ownership"), `git config --local` fails silently, and the unguarded
|
||||
`git config core.hooksPath hooks` exited 128 under `set -e`, aborting the
|
||||
installer one line before `Done`. Damage was nil (all nine extension symlinks
|
||||
were already created) but the WARN fired on v1.9.3 and v1.9.4 alike and would
|
||||
have masked a real installer failure. `activate_hooks` now resolves the git dir
|
||||
and requires a writable `.git/config`, else prints one note and returns 0 —
|
||||
hooks are for clones you commit from. Measured with the function extracted and
|
||||
run under `set -euo pipefail`: old function rc=128 on `/opt/pi-extensions` and
|
||||
on a root-owned throwaway; new function rc=0 on those two, on a dir without
|
||||
`.git`, and on a clone with `.git/config` mode 444; still activates on a
|
||||
writable unset clone (config reads back `hooks`) and reports already-active on
|
||||
a preset one; no root-owned config was touched.
|
||||
- **`Initializing MemPalace for workspace` re-fired on every boot of a container
|
||||
that had never mined locally** — `entrypoint-user.sh`. The first-run test
|
||||
checked `$PALACE_DIR/palace`, but `mempalace init` writes `config.json` and
|
||||
never creates `palace/` (mining does). v1.9.4 acceptance measured 1
|
||||
"Initializing" line after one boot and 2 after a restart. Idempotent, so
|
||||
harmless, but the comment claimed it skipped. Sentinel is now `config.json`
|
||||
(what init writes; also a 3.10.0 legacy-layout marker). Populated volumes skip
|
||||
either way.
|
||||
- **`developer` can now run git in the root-owned `/opt` clones** —
|
||||
`Dockerfile.variant` adds `git config --system --add safe.directory` for
|
||||
`/opt/{pi-toolkit,pi-extensions,pi-fork,pi-observational-memory,pi-atelier,mempalace-toolkit,pi-studio}`
|
||||
(paths listed, not `*`, so the ownership check still protects `/workspace`;
|
||||
the pi-studio entry is inert in the plain variant). Before: `git -C
|
||||
/opt/pi-atelier rev-parse --short HEAD` as `developer` printed nothing, which
|
||||
produced one false FAIL in the v1.9.4 acceptance and an earlier
|
||||
"dubious ownership" wall on `/opt/mempalace-toolkit`. Mechanism verified live
|
||||
in a v1.9.3 container (add → `25c1265`, unset → fatal again).
|
||||
|
||||
### CI
|
||||
|
||||
- **`base-decide` pins the collation of the hash input order:
|
||||
`find -print0 | LC_ALL=C sort -z`** — `docker-publish.yml`. `sort` collates per
|
||||
locale, and the order of `rootfs` files under a dictionary locale differs from
|
||||
byte order, so identical content hashed differently: measured
|
||||
`base-f40c4b7b103d` under `C` and `C.UTF-8` (== CI run 695) versus
|
||||
`base-d8df62216a81` under `sv_SE.UTF-8` and `en_US.UTF-8`. The runner image
|
||||
happens to export `LANG=C.UTF-8`, so the hash was stable by accident and the
|
||||
comment "order is fixed via `sort -z`" was not true as written. The pin is
|
||||
hash-neutral today (no spurious base rebuild from this commit; the workflow
|
||||
file is not a hash input) and removes the dependency on the runner's locale.
|
||||
Predicting `base_tag` locally must use the same per-command prefix — it is
|
||||
scoped to that one `sort`, not to the shell, so it leaves `sv_SE.UTF-8`
|
||||
everywhere else alone.
|
||||
|
||||
### Still open
|
||||
|
||||
- **pi 0.87.x + pi-observational-memory** — unchanged from v1.9.4: blocked on
|
||||
upstream [PR #83](https://github.com/elpapi42/pi-observational-memory/pull/83)
|
||||
(`shouldStopAfterTurn` → `finishTurn`); pi and pi-obsmem bump together.
|
||||
- **synlig hub** — upgraded to mempalace 3.10.0 on 2026-09-22T14:42:53Z (uv
|
||||
tool, hot backup first, 3 s downtime, 40996 embedding rows before == after).
|
||||
Client-visible: `event_list` without a cursor now returns newest first;
|
||||
search results carry `filed_at` / `authored_at_source` / `content_date`.
|
||||
Recorded here because v1.9.4's "still open" listed it.
|
||||
|
||||
---
|
||||
|
||||
## v1.9.4 — 2026-09-22
|
||||
|
||||
### Dependency audit (2026-09-22)
|
||||
|
||||
@@ -314,6 +314,22 @@ RUN set -e && \
|
||||
echo "pi-observational-memory at $(cd /opt/pi-observational-memory && git rev-parse --short HEAD)" && \
|
||||
echo "pi-atelier at $(cd /opt/pi-atelier && git rev-parse --short HEAD) (${PI_ATELIER_VERSION})"
|
||||
|
||||
# ── git: let the unprivileged user read the root-owned /opt clones ──────────
|
||||
# The clones above (and /opt/mempalace-toolkit from the base, /opt/pi-studio in
|
||||
# the studio variant) are root-owned; the container runs as `developer`. Since
|
||||
# git 2.35.2 (CVE-2022-24765) any git command in a repo owned by another user
|
||||
# fails with "dubious ownership" — so `git -C /opt/pi-extensions rev-parse`
|
||||
# returns nothing, `install.sh` used to abort on it, and acceptance checks that
|
||||
# read a baked ref via git silently measured "" (v1.9.4 first-boot run: one
|
||||
# false FAIL from exactly this). Listing the paths (not `*`) keeps the check
|
||||
# meaningful for everything else, e.g. the virtiofs-mounted /workspace.
|
||||
# Entries for paths absent in a variant (pi-studio) are inert.
|
||||
RUN for d in pi-toolkit pi-extensions pi-fork pi-observational-memory pi-atelier \
|
||||
mempalace-toolkit pi-studio; do \
|
||||
git config --system --add safe.directory "/opt/${d}"; \
|
||||
done && \
|
||||
git config --system --get-all safe.directory
|
||||
|
||||
# ── Image-baked skill refresh: pi-extensions (Option 1 over Option 2) ──
|
||||
# rootfs ships a VENDORED snapshot of the pi-extensions skill at
|
||||
# /usr/local/share/pi-devbox/skills/pi-extensions/ (the "floor" — guarantees the
|
||||
|
||||
+7
-1
@@ -108,7 +108,13 @@ if command -v mempalace &>/dev/null && [ -d /workspace ]; then
|
||||
# own resolution can no longer disagree about where the palace lives — a
|
||||
# disagreement that would make this branch fire on every start.
|
||||
PALACE_DIR="${MEMPALACE_CONFIG_DIR:-${HOME}/.mempalace}"
|
||||
if [ ! -d "$PALACE_DIR/palace" ]; then
|
||||
# Sentinel = config.json, because that is what `mempalace init` writes.
|
||||
# It does NOT create palace/ — mining does — so the earlier test on palace/
|
||||
# re-fired on every start of a container that had never mined locally
|
||||
# (v1.9.4 acceptance: 1 "Initializing" line after one boot, 2 after a
|
||||
# restart). Harmless (init is idempotent) but the log lied. Populated
|
||||
# volumes (config.json present) skip either way.
|
||||
if [ ! -f "$PALACE_DIR/config.json" ]; then
|
||||
echo "Initializing MemPalace for workspace (non-interactive)..."
|
||||
# </dev/null: mempalace init has an interactive "Mine this directory
|
||||
# now? [Y/n]" prompt that --yes does not auto-answer in all paths.
|
||||
|
||||
Reference in New Issue
Block a user