6.1 KiB
Post-Phase 20: native build result promotion into first-class Fruix store objects
Date: 2026-04-05
Goal
Make native FreeBSD base-build results feel like real Fruix objects instead of stopping at mutable staged files under:
/var/lib/fruix/native-builds/...
The desired model is:
/var/lib/fruix/native-builds/...remains a staging/result area/frx/store/...remains the real immutable identity
Validated artifact identities:
worldkernelheadersbootloader
What changed
Promotion metadata in guest result roots
The guest self-hosted helper now emits a promotion description file at:
/var/lib/fruix/native-builds/<run-id>/promotion.scm
That metadata records at least:
- executor / executor-version
- run-id / guest-host-name
- closure path
- development profile path
- declared FreeBSD base metadata
- source store provenance
- build policy
- artifact entries for:
worldkernelheadersbootloader
The helper also stages a promotable world artifact tree in addition to the already validated narrower artifacts.
Host-side promotion API
Fruix now exports:
promote-native-build-result
and the CLI now exposes:
fruix native-build promote RESULT_ROOT [--store DIR]
Promoted store object layout
Promotion now creates immutable store objects for:
/frx/store/...-fruix-native-world-.../frx/store/...-fruix-native-kernel-.../frx/store/...-fruix-native-headers-.../frx/store/...-fruix-native-bootloader-...
Each promoted artifact store records:
.fruix-native-build-object.scm.references
Promotion also creates a result-bundle store object:
/frx/store/...-fruix-native-build-result-...
That bundle records:
.fruix-native-build-result.scmartifacts/worldartifacts/kernelartifacts/headersartifacts/bootloader
where the artifacts/* entries are symlinks to the promoted artifact stores.
Identity policy
Artifact identity is now based on Fruix metadata plus a tree-content signature of the staged artifact tree.
That means promotion identity depends on both:
- the explicit Fruix-native build metadata
- the actual content of the promoted artifact tree
Validation harness
Added:
tests/system/run-phase20-native-build-store-promotion-xcpng.sh
This harness:
- boots the approved real XCP-ng guest path
- runs the validated in-guest self-hosted native build helper
- imports the guest result root back to the host
- runs
fruix native-build promote - verifies promoted store paths, metadata, symlink structure, and representative hashes
Validation
Passing run:
PASS phase20-native-build-store-promotion-xcpng- workdir:
/tmp/current-phase20-native-build-store-promotion-xcpng
Approved real XCP-ng path:
- VM
90490f2e-e8fc-4b7a-388e-5c26f0157289 - VDI
0f1f90d3-48ca-4fa2-91d8-fc6339b95743
Representative metadata:
run_id=20260405T213444Z
source_store=/frx/store/12d7704362e95afc2697db63f168b878e082b372-freebsd-source-default
guest_result_root=/var/lib/fruix/native-builds/20260405T213444Z
result_store=/frx/store/c6329a0053720b05aff3274b8b1d522c909f475d-fruix-native-build-result-15.0-STABLE-guest-self-hosted
world_store=/frx/store/dfe37b36f6537a95ceea16ea62001b2ca5617eb7-fruix-native-world-15.0-STABLE-guest-self-hosted
kernel_store=/frx/store/0ab7cbceca240ab2c3b91e83e059844ea792e49e-fruix-native-kernel-15.0-STABLE-guest-self-hosted
headers_store=/frx/store/5bbeae9266687a229f1c6d176a08886c35243ff0-fruix-native-headers-15.0-STABLE-guest-self-hosted
bootloader_store=/frx/store/bd49a508bd7a3b94a2535d6774f31c993c406552-fruix-native-bootloader-15.0-STABLE-guest-self-hosted
artifact_store_count=4
sha_kernel=16950f116a52134b98e2f8e0dacc556e18fe254e4a0ac2c1741422dde281a341
sha_loader=ea417846167ece270ada611624dca622ca38bd30125b9a125cd8ebb8b3600313
sha_param=9eb140ca7d9666f3d484a4174c9acd94b45427db6292b4e17de19af2c6aa5219
promoted_kernel_sha=16950f116a52134b98e2f8e0dacc556e18fe254e4a0ac2c1741422dde281a341
promoted_loader_sha=ea417846167ece270ada611624dca622ca38bd30125b9a125cd8ebb8b3600313
promoted_param_sha=9eb140ca7d9666f3d484a4174c9acd94b45427db6292b4e17de19af2c6aa5219
native_build_store_promotion=ok
Validated facts:
- guest self-hosted native-build results remain available under
/var/lib/fruix/native-builds/...as mutable staging/results - those staged results are now sufficient to promote first-class Fruix identities on the host
- promotion creates four immutable artifact store objects plus one immutable result-bundle store object
- promoted metadata retains executor/source/build-policy/provenance information explicitly
- promoted kernel, bootloader, and headers hashes match the already validated staged artifacts
- the promoted
worldartifact is now also preserved as a first-class Fruix store object instead of remaining only as an unpromoted staged tree
Important implementation note
The first full promotion attempt exposed an incorrect assumption in the validation surface:
- the promoted/staged
worldartifact should be checked at:bin/sh
- not at:
usr/bin/sh
That path expectation was corrected in:
- helper validation
- emitted promotion metadata
- the end-to-end promotion harness
Result
Fruix now has a validated native-build object model with a clear split:
- mutable native-build result roots under
/var/lib/fruix/native-builds/... - immutable promoted identities under
/frx/store/...
That makes native FreeBSD base builds feel substantially more Fruix-native:
- build outputs have explicit immutable identities
- metadata is Fruix-native rather than implied only by ad hoc directory layout
- executor/source/provenance/build-policy remain attached to the promoted result
- the staged result area and the real store identity are now intentionally distinct
Next direction
This suggests the next product step is not merely “more self-hosting”.
It is to generalize this result model so different execution modes can converge on the same promoted object story, for example:
- host-initiated in-guest builds
- guest self-hosted builds
- future executor variants
That would move Fruix closer to a shared executor model rather than treating each validation path as a one-off harness.