diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 8351f77..6220766 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -1791,3 +1791,79 @@ Current assessment: - Phase 6.2 is now satisfied on the current FreeBSD prototype track - the next step is to validate a minimal user-facing profile installation flow on top of these real store outputs + +## 2026-04-01 — Phase 6.3 completed: minimal profile installation validated on real store outputs + +Completed work: + +- added a runnable real-store profile harness: + - `tests/packages/run-phase6-real-store-profile-prototype.sh` +- wrote the Phase 6.3 report: + - `docs/reports/phase6-real-store-profile-freebsd.md` +- updated the Phase 6 package-build harnesses so they can recover derivation paths even when the requested outputs are already present in `/frx/store` +- ran the real-store profile harness successfully and captured metadata under: + - `/tmp/phase6-real-store-profile-metadata.txt` + +Important findings: + +- the current FreeBSD track now has a minimal user-facing profile installation flow built on top of real Phase 6 store outputs rather than the earlier Phase 3 package/profile prototype inputs +- the validated transaction semantics are intentionally small but real: + - generation 1 is created from the Phase 6.1 host-built store item + - generation 2 is created from the Phase 6.2 jail-built store item + - the `profile` symlink switches to generation 2 + - both generations remain addressable +- observed metadata confirmed: + - `profile_target=profile-2-link` + - `generation1_store_path=/frx/store/...-hello-2.12.3` + - `generation2_store_path=/frx/store/...-hello-2.12.3` + - `current_store_path=/frx/store/...-hello-2.12.3` + - `profile_hello_output=Hello, world!` + - `clean_env_hello_output=Hello, world!` +- the upstream-derived profile layer is still not fully usable on this FreeBSD track because the current `guix profiles` / `fruix package` path still reaches the unresolved bootstrap-platform blocker: + - `dynamic linker name not known for this system "x86_64-freebsd15.0"` +- despite that blocker, the minimal Fruix-owned profile path is now validated on top of real daemon-built store items + +Current assessment: + +- Phase 6.3 is now satisfied on the current FreeBSD prototype track +- Phase 6 as a whole is now complete on the active FreeBSD amd64 prototype path + +## 2026-04-01 — Phase 6 completed on the current FreeBSD prototype track + +Phase 6 is now considered complete for the active FreeBSD amd64 prototype path. + +Why this milestone is satisfied: + +- **Phase 6.1** success criteria were met on the prototype track: + - a real package definition derived from Guix's `hello` package now builds through `fruix build` + - the output lands in `/frx/store` + - the package runs from the store and preserves a declared source reference +- **Phase 6.2** success criteria were met on the prototype track: + - a real package build now executes inside a FreeBSD jail + - the build work runs under dropped numeric build credentials + - the jailed build succeeds into `/frx/store` +- **Phase 6.3** success criteria were met on the prototype track: + - real Phase 6 store outputs can be installed into a minimal profile environment + - generation switching works in a concrete form + - package execution through the profile succeeds for the current user + +Important scope note: + +- this completes the **real FreeBSD-backed store-build milestone** on the current prototype track, not full upstream-package-graph support or full upstream profile-layer parity yet +- the current package path still relies on a prefetched local source tarball for GNU Hello because the built-in downloader/root-daemon path remains a separate FreeBSD issue +- the current profile-installation path is a Fruix-owned minimal layer over real store outputs because the upstream-derived profile code still hits the unresolved FreeBSD bootstrap-platform mapping blocker +- nevertheless, the core Phase 6 question has now been answered positively: + - real package definitions can be built into `/frx/store` + - those builds can run under integrated jail/build-user isolation + - and the resulting store items can be exposed through a minimal user-facing profile flow + +Next recommended step: + +1. begin Phase 7.1 by defining a minimal Fruix operating-system model for FreeBSD +2. carry forward the selective Fruix naming policy: + - Fruix at the product boundary + - `/frx` as the canonical store root + - stable upstream-derived internal names unless there is strong architectural value in renaming them +3. keep the two remaining Phase 6 follow-up blockers visible but scoped: + - built-in downloader/root-daemon integration for real package origins + - upstream-derived profile/bootstrap-platform support for `x86_64-freebsd15.0` diff --git a/docs/reports/phase6-real-store-profile-freebsd.md b/docs/reports/phase6-real-store-profile-freebsd.md new file mode 100644 index 0000000..011da64 --- /dev/null +++ b/docs/reports/phase6-real-store-profile-freebsd.md @@ -0,0 +1,70 @@ +# Phase 6.3: Minimal profile installation validated on top of real store outputs + +Date: 2026-04-01 + +## Summary + +This step validates a minimal user-facing profile installation flow using the real `/frx/store` outputs produced in Phases 6.1 and 6.2. + +Added file: + +- `tests/packages/run-phase6-real-store-profile-prototype.sh` + +## Validation command + +Run command: + +```sh +METADATA_OUT=/tmp/phase6-real-store-profile-metadata.txt \ +./tests/packages/run-phase6-real-store-profile-prototype.sh +``` + +## What the harness does + +The harness: + +1. reruns the Phase 6.1 real-package build harness +2. reruns the Phase 6.2 jail-integrated package build harness +3. creates a minimal Fruix-owned profile transaction layout using those real store outputs +4. installs generation 1 from the Phase 6.1 store item +5. installs generation 2 from the Phase 6.2 store item +6. switches the current `profile` symlink to generation 2 +7. validates: + - preserved generation links + - current-profile switching + - `PATH` usability + - execution from a nearly empty environment + +## Observed results + +Observed metadata included: + +- `profile=.../profile` +- `profile_target=profile-2-link` +- `generation1_store_path=/frx/store/...-hello-2.12.3` +- `generation2_store_path=/frx/store/...-hello-2.12.3` +- `current_store_path=/frx/store/...-hello-2.12.3` +- `profile_hello_output=Hello, world!` +- `clean_env_hello_output=Hello, world!` +- `installation_mode=minimal-real-store-profile` + +## Important findings + +- the current FreeBSD track now has a minimal profile-generation and profile-switching story that operates on real store outputs rather than the earlier Phase 3 package/profile prototype layer +- the profile transaction semantics are intentionally small but real: + - generation 1 is retained + - generation 2 is created + - the `profile` symlink is switched to generation 2 + - executables become reachable through `PATH` +- this step intentionally uses a Fruix-owned minimal profile harness because the upstream-derived `guix profiles` / `fruix package` path still runs into the unresolved FreeBSD bootstrap-platform issue: + - `dynamic linker name not known for this system "x86_64-freebsd15.0"` +- even with that upstream profile-layer gap still open, the user-facing package-installation boundary is now validated in minimal form on top of real daemon-built store items + +## Conclusion + +Phase 6.3 is satisfied on the current FreeBSD prototype track: + +- packages built through the real store path can be installed into a profile-like user environment +- generation switching works in a minimal but concrete form +- package execution through the profile succeeds for the current user +- Phase 6 as a whole is now complete on the active FreeBSD prototype path, with deeper upstream profile-layer integration remaining follow-up work rather than a blocker to this milestone diff --git a/tests/guix/run-phase6-jail-package-build.sh b/tests/guix/run-phase6-jail-package-build.sh index e2c1e1e..9998bc7 100755 --- a/tests/guix/run-phase6-jail-package-build.sh +++ b/tests/guix/run-phase6-jail-package-build.sh @@ -24,6 +24,7 @@ daemon_log=$workdir/guix-daemon.log fetch_tarball=$workdir/hello-2.12.3.tar.gz build_stdout=$workdir/fruix-build.out build_stderr=$workdir/fruix-build.err +drv_stdout=$workdir/fruix-build-derivation.out references_log=$workdir/references.log package_template=$workdir/phase6-jail-hello.scm.in package_file=$workdir/phase6-jail-hello.scm @@ -229,10 +230,12 @@ done } GUIX_DAEMON_SOCKET=unix://$daemon_socket \ -./pre-inst-env fruix build -f "$package_file" >"$build_stdout" 2>"$build_stderr" +./pre-inst-env fruix build -d -f "$package_file" >"$drv_stdout" 2>>"$build_stderr" +GUIX_DAEMON_SOCKET=unix://$daemon_socket \ +./pre-inst-env fruix build -f "$package_file" >"$build_stdout" 2>>"$build_stderr" out_path=$(awk 'NF { last = $0 } END { print last }' "$build_stdout") -drv_path=$(sed -n 's/^successfully built //p' "$build_stderr" | tail -n 1) +drv_path=$(awk 'NF { last = $0 } END { print last }' "$drv_stdout") runtime_output=$("$out_path/bin/hello") source_store_path=$(tr -d '\n' < "$out_path/share/fruix-phase6/source-store-path.txt") build_uid=$(tr -d '\n' < "$out_path/share/fruix-phase6/build-uid.txt") @@ -242,7 +245,7 @@ build_mode=$(tr -d '\n' < "$out_path/share/fruix-phase6/build-mode.txt") GUIX_DAEMON_SOCKET=unix://$daemon_socket \ ./pre-inst-env fruix gc --references "$out_path" >"$references_log" 2>"$workdir/fruix-gc.err" -[ -n "$drv_path" ] || { echo "missing derivation path in $build_stderr" >&2; exit 1; } +[ -n "$drv_path" ] || { echo "missing derivation path in $drv_stdout" >&2; exit 1; } case "$drv_path" in /frx/store/*.drv) : ;; *) echo "unexpected derivation path: $drv_path" >&2; exit 1 ;; @@ -283,6 +286,7 @@ helper_source=$helper_source package_file=$package_file build_stdout=$build_stdout build_stderr=$build_stderr +drv_stdout=$drv_stdout references_log=$references_log drv_path=$drv_path out_path=$out_path diff --git a/tests/guix/run-phase6-real-package-build.sh b/tests/guix/run-phase6-real-package-build.sh index 0943640..5f94fb2 100755 --- a/tests/guix/run-phase6-real-package-build.sh +++ b/tests/guix/run-phase6-real-package-build.sh @@ -24,6 +24,7 @@ daemon_log=$workdir/guix-daemon.log fetch_tarball=$workdir/hello-2.12.3.tar.gz build_stdout=$workdir/fruix-build.out build_stderr=$workdir/fruix-build.err +drv_stdout=$workdir/fruix-build-derivation.out references_log=$workdir/references.log package_template=$workdir/phase6-hello.scm.in package_file=$workdir/phase6-hello.scm @@ -151,16 +152,18 @@ done } GUIX_DAEMON_SOCKET=unix://$daemon_socket \ -./pre-inst-env fruix build -f "$package_file" >"$build_stdout" 2>"$build_stderr" +./pre-inst-env fruix build -d -f "$package_file" >"$drv_stdout" 2>>"$build_stderr" +GUIX_DAEMON_SOCKET=unix://$daemon_socket \ +./pre-inst-env fruix build -f "$package_file" >"$build_stdout" 2>>"$build_stderr" out_path=$(awk 'NF { last = $0 } END { print last }' "$build_stdout") -drv_path=$(sed -n 's/^successfully built //p' "$build_stderr" | tail -n 1) +drv_path=$(awk 'NF { last = $0 } END { print last }' "$drv_stdout") source_store_path=$(tr -d '\n' < "$out_path/share/fruix-phase6/source-store-path.txt") runtime_output=$("$out_path/bin/hello") GUIX_DAEMON_SOCKET=unix://$daemon_socket \ ./pre-inst-env fruix gc --references "$out_path" >"$references_log" 2>"$workdir/fruix-gc.err" -[ -n "$drv_path" ] || { echo "missing derivation path in $build_stderr" >&2; exit 1; } +[ -n "$drv_path" ] || { echo "missing derivation path in $drv_stdout" >&2; exit 1; } case "$drv_path" in /frx/store/*.drv) : ;; *) echo "unexpected derivation path: $drv_path" >&2; exit 1 ;; @@ -193,6 +196,7 @@ fetch_tarball=$fetch_tarball package_file=$package_file build_stdout=$build_stdout build_stderr=$build_stderr +drv_stdout=$drv_stdout references_log=$references_log drv_path=$drv_path out_path=$out_path diff --git a/tests/packages/run-phase6-real-store-profile-prototype.sh b/tests/packages/run-phase6-real-store-profile-prototype.sh new file mode 100755 index 0000000..91f8e7a --- /dev/null +++ b/tests/packages/run-phase6-real-store-profile-prototype.sh @@ -0,0 +1,144 @@ +#!/bin/sh +set -eu + +repo_root=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd) +metadata_target=${METADATA_OUT:-} + +cleanup=0 +if [ -n "${WORKDIR:-}" ]; then + workdir=$WORKDIR + mkdir -p "$workdir" +else + workdir=$(mktemp -d /tmp/fruix-phase6-profile.XXXXXX) + cleanup=1 +fi +if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then + cleanup=0 +fi + +metadata_file=$workdir/phase6-real-store-profile-metadata.txt +phase61_metadata=$workdir/phase6-real-package-metadata.txt +phase62_metadata=$workdir/phase6-jail-package-metadata.txt +profile=$workdir/profile +phase61_log=$workdir/phase6-real-package.log +phase62_log=$workdir/phase6-jail-package.log + +cleanup_workdir() { + if [ "$cleanup" -eq 1 ]; then + rm -rf "$workdir" + fi +} +trap cleanup_workdir EXIT INT TERM + +install_generation() { + generation=$1 + store_path=$2 + label=$3 + generation_dir=$workdir/profile-generation-$generation + generation_link=$profile-$generation-link + + rm -rf "$generation_dir" + mkdir -p "$generation_dir" + for entry in "$store_path"/*; do + [ -e "$entry" ] || continue + ln -s "$entry" "$generation_dir/$(basename "$entry")" + done + cat >"$generation_dir/.fruix-profile-entry" <"$phase61_log" 2>&1 +METADATA_OUT=$phase62_metadata WORKDIR=$workdir/phase6-62 KEEP_WORKDIR=1 \ + "$repo_root/tests/guix/run-phase6-jail-package-build.sh" >"$phase62_log" 2>&1 + +phase61_out=$(sed -n 's/^out_path=//p' "$phase61_metadata") +phase62_out=$(sed -n 's/^out_path=//p' "$phase62_metadata") +phase61_runtime=$(sed -n 's/^runtime_output=//p' "$phase61_metadata") +phase62_runtime=$(sed -n 's/^runtime_output=//p' "$phase62_metadata") + +[ "$phase61_runtime" = 'Hello, world!' ] || { + echo "phase6.1 prerequisite failed: $phase61_runtime" >&2 + exit 1 +} +[ "$phase62_runtime" = 'Hello, world!' ] || { + echo "phase6.2 prerequisite failed: $phase62_runtime" >&2 + exit 1 +} + +install_generation 1 "$phase61_out" phase6-real-package +install_generation 2 "$phase62_out" phase6-jail-package + +profile_target=$(readlink "$profile") +generation1_target=$(readlink "$profile-1-link") +generation2_target=$(readlink "$profile-2-link") +current_bin_target=$(readlink "$profile/bin") +current_store_path=$(dirname "$current_bin_target") +profile_hello_output=$(PATH="$profile/bin:/bin:/usr/bin" hello) +clean_env_hello_output=$(env -i PATH="$profile/bin:/bin:/usr/bin" HOME="$workdir/home" USER="${USER:-self}" LOGNAME="${LOGNAME:-self}" "$profile/bin/hello") +user_uid=$(id -u) +user_name=$(id -un) + +[ "$profile_target" = 'profile-2-link' ] || { + echo "unexpected current profile target: $profile_target" >&2 + exit 1 +} +[ "$current_store_path" = "$phase62_out" ] || { + echo "profile does not point at the expected current store path" >&2 + exit 1 +} +[ "$profile_hello_output" = 'Hello, world!' ] || { + echo "unexpected profile hello output: $profile_hello_output" >&2 + exit 1 +} +[ "$clean_env_hello_output" = 'Hello, world!' ] || { + echo "unexpected clean-env hello output: $clean_env_hello_output" >&2 + exit 1 +} + +cat >"$metadata_file" <