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
|
id: compute
|
||||||
run: |
|
run: |
|
||||||
# Hash inputs that determine the base image's contents.
|
# 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
|
# Junk filters: __pycache__/*.pyc and macOS metadata are gitignored
|
||||||
# locally but still picked up by `find rootfs -type f` on a clean CI
|
# locally but still picked up by `find rootfs -type f` on a clean CI
|
||||||
# checkout. Exclude them defensively.
|
# checkout. Exclude them defensively.
|
||||||
@@ -120,7 +127,7 @@ jobs:
|
|||||||
! -name '*.pyc' \
|
! -name '*.pyc' \
|
||||||
! -name '.DS_Store' \
|
! -name '.DS_Store' \
|
||||||
! -name '._*' \
|
! -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
|
cat entrypoint.sh entrypoint-user.sh
|
||||||
# mempalace-toolkit is cloned in Dockerfile.base at a ref CI
|
# mempalace-toolkit is cloned in Dockerfile.base at a ref CI
|
||||||
# resolves to a SHA; fold it in so base_tag changes when the
|
# 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
|
## v1.9.4 — 2026-09-22
|
||||||
|
|
||||||
### Dependency audit (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-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})"
|
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) ──
|
# ── Image-baked skill refresh: pi-extensions (Option 1 over Option 2) ──
|
||||||
# rootfs ships a VENDORED snapshot of the pi-extensions skill at
|
# rootfs ships a VENDORED snapshot of the pi-extensions skill at
|
||||||
# /usr/local/share/pi-devbox/skills/pi-extensions/ (the "floor" — guarantees the
|
# /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
|
# own resolution can no longer disagree about where the palace lives — a
|
||||||
# disagreement that would make this branch fire on every start.
|
# disagreement that would make this branch fire on every start.
|
||||||
PALACE_DIR="${MEMPALACE_CONFIG_DIR:-${HOME}/.mempalace}"
|
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)..."
|
echo "Initializing MemPalace for workspace (non-interactive)..."
|
||||||
# </dev/null: mempalace init has an interactive "Mine this directory
|
# </dev/null: mempalace init has an interactive "Mine this directory
|
||||||
# now? [Y/n]" prompt that --yes does not auto-answer in all paths.
|
# now? [Y/n]" prompt that --yes does not auto-answer in all paths.
|
||||||
|
|||||||
Reference in New Issue
Block a user