homebrew: make bootstrap images reproducible and provenance-bound#921
Conversation
Treat manifest and ZIP paths as untrusted archive input: require canonical absolute manifest paths, byte-exact UTF-8 canonical relative ZIP names, and compatible path graphs before VFS mutation. Preserve validated Unix symlink targets and ownership rather than flattening them into regular files. This removes the generic image-builder blocker found while reviewing the Homebrew bootstrap in #902. It does not change the Kandelo ABI.
Add an exclusive --binaries-dir input to the rootfs package manifest generator. Release-image builders can now bind eager sources and lazy sizes to the same resolver tree they validate, while ordinary development retains the existing local override priority. Reject noncanonical or escaping output paths and cover selected-tree, no-fallback, eager, lazy, and path-identity behavior.
Select the canonical binaries/ tree when generating the bootstrap rootfs manifest, so eager sources and lazy sizes come from the same artifacts the builder resolves and ABI-validates. This prevents local-binaries overrides from contaminating either supported build mode.
Allow Node to flush asynchronous stdout writes before normal shutdown so large inspect output is not truncated. Treat stdout EPIPE as normal pipeline termination when a consumer closes early, while preserving other stream failures.
Keep fixed archive file modes as the safe default. Let explicitly trusted ZIPs add only Unix execute bits to the manifest-normalized file mode, while ownership and all other permission bits remain declarative. Use the policy for the pinned Homebrew bootstrap so bin/brew and all upstream executable shims work without one-file overrides.
Mirror the ES2024 SharedMemory library surface used by the imported host VFS implementation. Keep the ES2022 code-generation target and existing compiler options unchanged.
Phase B-1 matrix build status —
|
| Package | Arch | Status | Sha |
|---|---|---|---|
| kandelo-sdk | wasm32 | built | 18405ee6 |
| rootfs | wasm32 | built | 0a05033f |
| shell | wasm32 | built | d96885ae |
| lamp | wasm32 | built | 801a444d |
| node-vfs | wasm32 | built | 8207665f |
| wordpress | wasm32 | built | 203f33a8 |
Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.
|
Updated the batch after devil's-advocate review found that the pinned Homebrew ZIP had 64 executable regular files while the bootstrap manifest normalized every archive file to The new opt-in Fresh normal and |
|
prepare-merge: test-gate passed against the synthetic PR merge and |
Purpose
Provide one reviewable, ABI39-based Homebrew bootstrap tooling batch. It builds an unmodified upstream Homebrew checkout into a Kandelo VFS image through the normal package resolver, binds every manifest entry to the selected artifact tree, preserves trusted ZIP filesystem semantics without importing unsafe permissions, and makes the resulting image byte-reproducible.
The branch is a linear nine-commit stack intended for rebase merge so each independently reviewed fix remains bisectable.
Preserved commits
--binaries-dirrootfs manifest provenance.binaries/tree.SOURCE_DATE_EPOCH.mkrootfs inspectoutput with correctEPIPEhandling.An independent exact audit verified that every source/candidate pair has the same stable patch ID, message, and author, and that no overlapping hunk was dropped or duplicated. A separate devil's-advocate review accepted the trusted archive permission boundary with no findings.
Integrated evidence
All validation ran through
scripts/dev-shell.shon ABI39.a151cb827da26b1412fe9a8c37ae7c9ee57097e092ccf0f3920a1b4901f78b6c, byte-identical0755; all 2,900 non-executables are06440755; every archive member is uid/gid 1000; zero mismatchesThe permission policy remains
fixedby default.preserve-executableis an explicit opt-in for a trusted ZIP and imports only0111from Unix central-directory entries. Read/write, set-ID, sticky, type, ownership, directory, symlink, and non-Unix compatibility metadata remain controlled by the manifest.Shell syntax, Node syntax, diff checks, deterministic timestamp adversarial probes, and the complete-output/early-pipe probes also passed.
Truthful runtime boundary
The image runs real upstream Homebrew, not a registry wrapper.
brew --versionandbrew config --no-install-from-apiboth reach Ruby and then exit 139 because each fork continuation needs 20,012 bytes while ABI39 reserves 16,384. The host reports that exact platform limit instead of corrupting memory. This batch intentionally does not claim that Homebrew installation is functional; continuation sizing belongs in the next ABI/rebuild batch.Contract scope
This PR does not change
ABI_VERSION, the ABI snapshot, or the VFS on-disk layout. Timestamp normalization is opt-in on a detached snapshot; ordinary Node and browser runtime snapshots preserve live POSIX times. Full browser runtime and broad conformance suites were not run locally and remain required CI evidence.Supersession
This batch is intended to supersede #902, #917, #919, and #920 after the combined CI run is green and maintainer review is complete. Keep those PRs open until then.