Files
fruix/docs/reports/phase20-native-build-store-promotion-freebsd.md

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:

  • world
  • kernel
  • headers
  • bootloader

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:
    • world
    • kernel
    • headers
    • bootloader

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.scm
  • artifacts/world
  • artifacts/kernel
  • artifacts/headers
  • artifacts/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:

  1. boots the approved real XCP-ng guest path
  2. runs the validated in-guest self-hosted native build helper
  3. imports the guest result root back to the host
  4. runs fruix native-build promote
  5. 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 world artifact 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 world artifact 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.