check-skill-floor.sh: OK, tree_sha256 9b85a633… matches the package at main (25c1265).
Forces one base rebuild (base_tag hashes rootfs/), which is also what bakes
extensions/task.ts and fork-gate.ts into /opt/pi-extensions via the floating
PI_EXTENSIONS_REF=main; install.sh symlinks every extensions/*.ts on start.
Two changes that share one forced base rebuild, hence one commit.
1. THREE PACKAGES, each closing a capability gap measured during the
gitea.egl.lan/FreeIPA work on 2026-09-09..10 rather than a preference:
bind9-dnsutils (~6.1 MB measured) -- dig/host/nslookup were ALL absent,
so the container could resolve names but had no way to interrogate a
SPECIFIC nameserver. `getent hosts` only follows the resolver's default
path, so diagnosing "gateway 172.16.88.1 NXDOMAINs the egl.lan zone
while 10.20.253.1 is authoritative for it" had to be hand-rolled in
python3. Split-horizon DNS is a recurring class of bug on this fleet.
Note the package name: plain `dnsutils` is transitional in trixie.
ldap-utils (1244 KB, pulls nothing extra) -- the fleet authenticates
against FreeIPA, yet every LDAP probe had to be run by SSHing to an
already-enrolled host. Simple binds only; GSSAPI would additionally
need krb5-user + libsasl2-modules-gssapi-mit, deliberately not added
as that is a Kerberos-client decision, not a tool.
xxd (198 KB) -- convenience for verifying git-crypt blob magic in
myconfigs; `od -c` from coreutils already does the same job.
netcat-openbsd was in the original proposal and is deliberately NOT
here: measured redundant, because socat is already baked and bash's
/dev/tcp does reachability checks with zero packages (verified against
gitea.egl.lan:3000). Recorded in the Dockerfile so the omission reads
as a decision rather than an oversight.
2. ROOTFS FLOOR REFRESH: rootfs/.../pi-extensions/SKILL.md was 34284 B,
unchanged since fa04d20 (2026-07-30), while the canonical package copy
is 38973 B. Dockerfile.variant copies the fresh package copy over the
SERVED path at build time but never writes back to this floor, so the
floor is a silent fallback: if that build-time copy is ever absent it
ships the July skill with no log line or manifest flag to say which
version deployed. Refreshed from pi-extensions@c64c122, verified
byte-identical to both the canonical and the runtime-served copies.
Why one commit: the base_tag hash folds in `cat Dockerfile.base` AND
`find rootfs -type f | xargs cat` (.gitea/workflows/docker-publish.yml),
so either change alone forces the same full base rebuild -- and that
rebuild is precisely what re-bakes rootfs/ as it then stands. Emulating
the workflow hash with a fixed toolkit ref: f3d6462c7416 -> fc4edda03c54.
Verified: scripts/check-base-hash.sh passes (no new ARG *_REF added), and
no shell scripts are touched so the lint-shell gate is unaffected. Sizes
and dependency fan-out measured via apt-get --no-install-recommends
--dry-run on Debian 13 trixie.
Vendored floor snapshot re-synced from pi-extensions 98eb07b, which documents
the mechanism behind fork boundary violations (full parent-transcript
inheritance via index.ts:47) plus the corrected claims about tool restriction
and narrative invention. CI resolves PI_EXTENSIONS_REF from main HEAD, so a
normal build ships the package-owned copy; this keeps the committed floor
identical so scripts/smoke-test.sh's cmp assertion holds either way.
Sync the image-baked "floor" copy at
rootfs/usr/local/share/pi-devbox/skills/pi-extensions/SKILL.md with
pi-extensions e73cb9f, which documents package-registration forensics
(packages[] vs whole-file grep), that /reload suffices for a newly installed
package, and a fork-output caveat — the findings from the fork-guard bug fixed
in 8248688.
Dockerfile.variant copies the pinned package's skill/ over this snapshot at
build time, so the vendored copy is only the fallback floor; it was byte-
identical to the package copy before this change, and keeping it in sync
prevents a silent divergence for builds whose PI_EXTENSIONS_REF predates the
skill.
The pi-toolkit global AGENTS.md tells every pi session to read
~/.agents/skills/pi-extensions/SKILL.md at start (the fork/recall
under-utilisation fix), but that skill lived only in the private skillset
repo — so the pointer dangled in any container started without skillset
mounted. Bake fallbacks so the pointer always resolves.
- pi-extensions (Option 1 + Option 2, layered):
* Canonical skill promoted to the public pi-extensions package repo under
skill/ (separate commit there); co-located with the code it documents.
* rootfs/ carries a committed snapshot (the floor).
* Dockerfile.variant copies /opt/pi-extensions/skill/ over the snapshot
after the pinned clone, so a normal build ships the fresh package copy
(recorded via PI_EXTENSIONS_REF) and an old-ref/mirror build still ships
the snapshot. Helper evaluate-extension-usage.py travels with it.
- mempalace (Option 2 only): snapshot in rootfs/. Its consumer skill has no
public package home (mempalace-toolkit ships a different skill,
opencode-mempalace-bridge), so no build-time refresh.
- entrypoint links both (only-when-absent; mounted skillset still wins).
- smoke-test: build-time presence + package-match check + runtime symlink
assertions; readiness gate now waits on the last-linked skill.
- docs: skills/VENDORED.md (provenance + refresh), README, AGENTS.md,
CHANGELOG [Unreleased].
Note: shipped in the NEXT release; v1.2.0 (run 409) predates this.