Files
fruix/docs/reports/phase20-native-build-executor-model-freebsd.md

5.5 KiB

Post-Phase 20: native build executor model

Date: 2026-04-06

Goal

Turn the now-proven native base-build placement options into one Fruix abstraction instead of treating them as unrelated paths.

The desired model is:

  • same declared source identity
  • same expected artifact kinds
  • same staged-result shape
  • same promotion/provenance shape
  • different executor policy

So the question becomes:

  • where should the build run?

and the answer is expressed as executor policy rather than as a separate architecture each time.

Executor model

Fruix now has an explicit native-build executor model with current executor kinds:

  • host
  • ssh-guest
  • self-hosted

and intended future extension points:

  • jail
  • remote-builder

The new executor model is implemented in:

  • modules/fruix/system/freebsd/executor.scm

It defines a structured executor object that records at least:

  • kind
  • name
  • version
  • properties

What changed

1. Structured executor metadata

Native-build result metadata is no longer limited to a flat string such as:

  • guest-self-hosted

Instead, result/promotion objects now carry a structured executor description.

Representative executor objects now look like:

((kind . self-hosted)
 (name . "guest-self-hosted")
 (version . "4")
 (properties . (...)))

or:

((kind . ssh-guest)
 (name . "ssh-guest")
 (version . "1")
 (properties . (...)))

Promoted store metadata also now records compatibility fields derived from that executor object:

  • executor-kind
  • executor-name
  • executor-version

2. Shared staged-result shape across executors

Both validated executor paths now converge on the same mutable staging layout under:

  • /var/lib/fruix/native-builds/<run-id>

with promoted artifacts staged under:

  • artifacts/world
  • artifacts/kernel
  • artifacts/headers
  • artifacts/bootloader

and promotion metadata recorded in:

  • promotion.scm

The heavy build work remains executor-specific, but the result shape is now shared.

3. Shared immutable promotion path

Both validated executor paths now promote through the same command:

fruix native-build promote RESULT_ROOT

That promotion creates immutable store identities for:

  • world
  • kernel
  • headers
  • bootloader
  • result bundle

under /frx/store/....

Validated executor policies

self-hosted

The self-hosted guest helper now emits the structured executor metadata directly from:

  • /usr/local/bin/fruix-self-hosted-native-build

Validated self-hosted promotion flow:

  • PASS phase20-self-hosted-native-build-xcpng
  • PASS phase20-native-build-store-promotion-xcpng

Representative promoted result:

result_store=/frx/store/0423193b9bd5e652bdb9d94d077e40dfcc3e9e78-fruix-native-build-result-15.0-STABLE-guest-self-hosted
world_store=/frx/store/5f67e95058186147206ff6f5da2243a09212e358-fruix-native-world-15.0-STABLE-guest-self-hosted
kernel_store=/frx/store/3a32797cc187a90e8273f205eababae6246568d9-fruix-native-kernel-15.0-STABLE-guest-self-hosted
headers_store=/frx/store/1d54d9814461f003e91add8cd37e94ac5f3d04ce-fruix-native-headers-15.0-STABLE-guest-self-hosted
bootloader_store=/frx/store/49f4885f0f05a324ac826f2618d4c7a923ca30d2-fruix-native-bootloader-15.0-STABLE-guest-self-hosted

ssh-guest

The host-initiated guest path now also stages a shared native-build result root under:

  • /var/lib/fruix/native-builds/<run-id>

instead of stopping at temporary build directories alone.

Its promotion metadata records executor policy as:

  • kind = ssh-guest
  • name = ssh-guest
  • version = 1

with executor properties including:

  • transport = ssh
  • orchestrator = host
  • guest addressing / VM identity metadata

Validated host-initiated promotion flow:

  • PASS phase20-host-initiated-native-build-xcpng
  • PASS phase20-host-initiated-native-build-store-promotion-xcpng

Representative promoted result:

result_store=/frx/store/ffe44f5d1ba576e1f811ad3fe3a526a242b5c4a5-fruix-native-build-result-15.0-STABLE-ssh-guest
world_store=/frx/store/89c7a71c3df148a1f99b13d57fd6be88243eb2cb-fruix-native-world-15.0-STABLE-ssh-guest
kernel_store=/frx/store/93bac81122022b40438d356146a6854b4ee48513-fruix-native-kernel-15.0-STABLE-ssh-guest
headers_store=/frx/store/dd7f39f526bca4849caf1eaf96ae25d29b43493c-fruix-native-headers-15.0-STABLE-ssh-guest
bootloader_store=/frx/store/78b1c6b0b5c0c2c1549f5f42f3d64b6d9293669b-fruix-native-bootloader-15.0-STABLE-ssh-guest

Important result

The same declared source and artifact contract now works across two different executor policies:

  • ssh-guest
  • self-hosted

while preserving the same Fruix-native staging/promotion split:

  • mutable result roots under /var/lib/fruix/native-builds/...
  • immutable promoted identities under /frx/store/...

That is the core architectural win of the executor model.

What is not yet fully unified

The host executor kind now exists in the model, but the fully shared staged-result-plus-promotion workflow is not yet wired through the existing host-local native build path.

So the executor model is introduced and real-VM validated across two policies, but not yet uniformly productized across every native-build entry point.

Result

Fruix now treats native-build placement as executor policy rather than as an architectural fork.

That means the next step is no longer to invent another build path from scratch.

It is to keep extending the same executor/result/promotion model so additional execution policies can plug into the same Fruix-native object story.