Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Forgeyard Hermetic Build, Functional Packaging & Reproducible Distribution Architecture

Document type: System & Architecture
Project: Forgeyard CI/CD
Subsystem: Hermetic builds, functional package management, immutable package store, derivations, lock graphs, reproducible packaging, binary substitution, promotion, and distribution
Implementation direction: Rust-first
Status: Target architecture
Scope note: This is a new subsystem document. It does not assume that next.md, or any other single Forgeyard architecture document, is the complete Forgeyard architecture.


1. Purpose

A CI/CD system is not reliable if a build succeeds because of invisible state on one machine.

The familiar symptom is:

“It works on my machine.”

The architectural causes are usually:

  • mutable operating-system state;
  • undeclared tools and SDKs;
  • globally installed libraries;
  • stale local caches;
  • moving Git branches and tags;
  • unpinned package repositories;
  • changing download URLs;
  • implicit environment variables;
  • filesystem ordering;
  • locale and timezone differences;
  • timestamps;
  • randomness;
  • absolute build paths;
  • UID/GID differences;
  • hidden /usr, /opt, $HOME, registry, SDK, or PATH dependencies;
  • arbitrary network access during builds;
  • mutable CI runner images.

Forgeyard should therefore include a native hermetic functional build and package subsystem.

It should adopt the strongest architectural ideas demonstrated by Nix-style functional package management:

  • package/build results are values rather than mutable installations;
  • builds are represented by derivations;
  • dependencies form explicit closures;
  • store objects are immutable;
  • identities are cryptographic;
  • external inputs are locked;
  • already built objects can be substituted from trusted caches;
  • garbage collection follows reachability rather than “delete old directories”.

Forgeyard should not simply wrap Nix. These ideas should be integrated directly with Forgeyard’s:

  • Pipeline IR;
  • scheduler;
  • runners;
  • sandbox;
  • CAS;
  • toolchain manager;
  • packaging;
  • provenance;
  • signing;
  • release approvals;
  • distribution;
  • deployment;
  • remote execution.

2. Central Architectural Rule

The ideal build is modeled as:

output = f(all_declared_inputs)

not:

output =
    f(
        declared_inputs,
        unknown_host_state,
        current_internet_state,
        current_wall_clock,
        random_machine_state
    )

A Forgeyard derivation is conceptually:

D = H(
    schema_version,
    builder,
    arguments,
    source_inputs,
    build_inputs,
    runtime_inputs,
    toolchains,
    declared_environment,
    build_platform,
    host_platform,
    target_platform,
    sandbox_policy,
    network_policy,
    output_contract
)

D identifies build intent and its declared input graph.

After realization:

O = H(canonical_output_tree)

O identifies the actual result.

Forgeyard MUST preserve both identities.

This distinction is critical:

same derivation identity
    does not by itself prove
same output bytes

A builder can still embed time, randomness, pathnames, filesystem order, or other nondeterministic information.

Therefore Forgeyard’s model is:

locked inputs
    +
canonical derivation
    +
hermetic execution
    +
content-addressed realized output
    +
independent reproduction
    +
provenance

3. Goals

Forgeyard MUST make the following possible.

3.1 Rebuild anywhere

Given:

  • project manifest;
  • lock graph;
  • source objects;
  • dependency objects;
  • toolchains;
  • compatible platform;

another clean runner should be able to realize the same derivation.

3.2 No hidden runner state

A strict build may see only declared inputs and explicitly permitted platform interfaces.

3.3 Immutable package identity

Updating a package creates a new object. Existing objects never change.

3.4 Explicit dependency closures

Forgeyard knows which objects are required to:

  • build;
  • test;
  • run;
  • package;
  • deploy.

3.5 Locked mutable sources

The production build cannot resolve main, latest, unbounded semver ranges, or mutable URLs during realization.

3.6 Measurable reproducibility

Forgeyard can answer:

Was this artifact independently rebuilt?
How many times?
On which runners?
Did all output digests match?
If not, which files differed?

3.7 Efficient reuse

Functional purity must coexist with:

  • local SSD stores;
  • shared CAS;
  • remote substitution;
  • P2P transfer;
  • closure-aware scheduling;
  • deterministic action caching.

3.8 Build once, promote many

A release artifact is built once, verified, then promoted by digest.


4. Non-Goals

This subsystem is not intended to:

  • implement the Nix language;
  • require NixOS;
  • replace Cargo/npm/Maven/Gradle/etc.;
  • force every development build to be completely pure;
  • pretend every ecosystem already produces deterministic bytes;
  • require Linux-specific mechanisms on Windows/macOS;
  • make OCI containers the only execution substrate.

Forgeyard creates a common reproducibility model across ecosystems.


5. Terminology

Derivation

Canonical description of a build and all declared build inputs.

Realization

One execution of a derivation.

Store object

Immutable content in Forgeyard’s functional store.

Closure

Transitive set of store objects required by another object.

Lock graph

Fully resolved immutable input graph.

Substitution

Reusing a trusted existing realization instead of building it locally.

Hermetic build

Build whose accessible inputs are restricted to declared inputs and controlled platform services.

Deterministic build

Equivalent execution is expected to produce equivalent output.

Reproducible build

Independent realizations have been compared and matched according to policy.

Promotion

Changing release/channel/deployment references to an existing immutable artifact without rebuilding it.


6. Reproducibility Levels

#![allow(unused)]
fn main() {
pub enum ReproducibilityLevel {
    Impure,
    Declared,
    Hermetic,
    DeterministicExpected,
    Reproduced,
    MultiPartyReproduced,
}
}

Suggested meaning:

LevelMeaning
ImpureHost/network state may affect output
DeclaredInputs declared, isolation incomplete
HermeticUndeclared filesystem/network state blocked
DeterministicExpectedHermetic + deterministic-output policy
ReproducedIndependent realization matched
MultiPartyReproducedMultiple independent realizations matched

Release policy may require:

local development       -> Declared
pull request CI         -> Hermetic
release candidate       -> DeterministicExpected
production release      -> Reproduced
high-assurance release  -> MultiPartyReproduced

7. New Logical Subsystem

Recommended capability name:

Forgeyard Reproducible Build System (FRBS)

Suggested crates:

forgeyard-derivation
forgeyard-lock
forgeyard-functional-store
forgeyard-hermetic
forgeyard-realizer
forgeyard-substituter
forgeyard-reproducibility
forgeyard-package
forgeyard-distribution
forgeyard-environment
forgeyard-platform

The exact crate count is not architectural truth; capability boundaries are.


8. High-Level Architecture

flowchart TB
    Manifest["forgeyard.ron / Pipeline IR"]
    Resolver["Input Resolver"]
    Lock["forgeyard.lock"]
    Compiler["Derivation Compiler"]
    Graph["Derivation DAG"]

    Subs["Trusted Substituters"]
    Sched["Forgeyard Scheduler"]
    Runner["Hermetic Runner"]
    Sandbox["Sandbox / VM / OS Adapter"]
    Realizer["Realizer"]

    Store["Immutable Functional Store"]
    Repro["Reproducibility Verifier"]
    Package["Deterministic Packager"]
    Provenance["SBOM + Provenance + Signing"]
    Dist["Promotion / Distribution"]

    Manifest --> Resolver
    Resolver --> Lock
    Lock --> Compiler
    Compiler --> Graph

    Graph --> Subs
    Subs -->|cache hit| Store
    Subs -->|miss| Sched
    Sched --> Runner
    Runner --> Sandbox
    Sandbox --> Realizer
    Realizer --> Store

    Store --> Repro
    Repro --> Package
    Package --> Provenance
    Provenance --> Dist

9. Relationship to Existing Forgeyard

The broader Forgeyard architecture already contains concepts such as:

pipeline compiler
scheduler
runner
sandbox
CAS
toolchains
packaging
artifacts
policy
signing
provenance
deployment

This subsystem strengthens their contracts.

Old conceptual behavior:

run commands on a compatible runner

Target:

realize a declared derivation inside a controlled environment

Forgeyard may continue supporting imperative jobs, but their reproducibility status is explicit.


10. Derivation Domain Model

#![allow(unused)]
fn main() {
pub struct Derivation {
    pub schema: DerivationSchemaVersion,

    pub name: PackageName,
    pub version: PackageVersion,

    pub builder: BuilderRef,
    pub arguments: Vec<Argument>,

    pub source_inputs: Vec<SourceInput>,
    pub build_inputs: Vec<StoreRef>,
    pub runtime_inputs: Vec<StoreRef>,
    pub toolchains: Vec<ToolchainRef>,

    pub environment: BTreeMap<EnvName, EnvValue>,

    pub build_platform: BuildPlatform,
    pub host_platform: HostPlatform,
    pub target_platform: TargetPlatform,

    pub sandbox: SandboxPolicy,
    pub network: NetworkPolicy,

    pub outputs: Vec<OutputSpec>,
    pub normalization: NormalizationPolicy,
    pub reproducibility: ReproducibilityPolicy,
}
}

Hash-sensitive structures use canonical ordering.


11. Canonical Derivation Encoding

Never hash arbitrary in-memory Rust representations.

Use:

Domain Derivation
      ↓
CanonicalDerivationV1
      ↓
deterministic binary encoding
      ↓
digest

The canonical form defines:

  • schema version;
  • field ordering;
  • map ordering;
  • string normalization;
  • integer representation;
  • optional-field behavior.

RON can remain the human-facing representation.

Postcard can be used internally if the Forgeyard canonical schema guarantees stable encoding.


12. Strong Identity Types

Use distinct newtypes:

#![allow(unused)]
fn main() {
pub struct DerivationId(Digest);
pub struct RealizationId(Uuid);
pub struct StoreObjectId(Digest);
pub struct LockGraphId(Digest);
pub struct SourceDigest(Digest);
pub struct OutputDigest(Digest);
pub struct PackageDigest(Digest);
pub struct ReleaseDigest(Digest);
}

Do not interchange them merely because they all contain hashes.


13. Hash Algorithms

Recommended:

BLAKE3 -> native Forgeyard internal identity
SHA-256 -> ecosystem/interoperability identity

Digest includes algorithm:

blake3:...
sha256:...

Forgeyard should support hash migration rather than assuming one algorithm forever.


14. Input Address vs Output Content Address

Forgeyard keeps:

DerivationId -> build intent and declared inputs
OutputDigest -> actual bytes/tree

Why both?

Input identity alone is insufficient

A build may embed current time:

same D
run A -> O1
run B -> O2
O1 != O2

Forgeyard must expose the mismatch.

Output identity alone is insufficient

O does not explain:

  • source;
  • toolchain;
  • command;
  • dependency graph;
  • policy.

Derivation + provenance explain how the output arose.


15. Realization Mapping

DerivationId
    │
    ├── Realization A -> OutputDigest X
    └── Realization B -> OutputDigest Y

If:

X == Y

reproducibility is confirmed for those attempts.

If:

X != Y

the result is quarantined according to policy.

Forgeyard MUST NOT silently overwrite the first mapping.


16. Functional Store

Logical namespace:

/forgeyard/store/<algorithm>-<digest>-<human-name>

The physical path may differ by platform.

Store identity is logical and portable.

Store objects are immutable.


17. Store Invariants

After commit:

object bytes: immutable
object references: immutable
content digest: immutable
semantic object identity: immutable

Mutable metadata may include:

trust status
pin/root state
replication status
last-access metrics
promotion references

Trust state can change without changing object bytes.


18. Store Object Kinds

#![allow(unused)]
fn main() {
pub enum StoreObjectKind {
    SourceTree,
    Toolchain,
    BuildDependency,
    RuntimeDependency,
    BuildOutput,
    RuntimeClosure,
    DebugSymbols,
    TestFixture,
    Package,
    Sbom,
    Provenance,
    LockGraph,
    Derivation,
    ReproductionDiff,
}
}

The existing Forgeyard CAS stores bytes.

The functional store adds semantic metadata, relationships, roots, and generations.


19. Store Architecture

                 Functional Store API
                         │
         ┌───────────────┼───────────────┐
         ▼               ▼               ▼
   metadata DB       local CAS       remote CAS
         │               │               │
         │               ├── SSD         ├── S3
         │               ├── site cache  ├── GCS
         │               └── iroh peer   └── Azure/MinIO
         │
         └──── object-reference graph

Do not create a second unrelated blob system.


20. Dependency Closures

For root object A:

closure(A) =
    A
    ∪ direct_dependencies(A)
    ∪ transitive_dependencies(A)

CLI:

forgeyard store closure <object>
forgeyard store graph <object>
forgeyard store why-depends <a> <b>

Closures support:

  • deployment;
  • air-gap export;
  • GC;
  • SBOM;
  • vulnerability analysis;
  • license analysis;
  • runtime verification.

21. Build Inputs vs Runtime Inputs

Keep them separate.

Example:

build inputs:
  rustc
  cargo
  pkg-config
  cmake

runtime inputs:
  libc
  openssl

This avoids shipping compilers and headers into production.


22. Multiple Outputs

A derivation can produce:

out
dev
debug
docs
tests

Each output is independently content-addressed.

This avoids bloating runtime closures with headers/debug symbols.


23. Garbage Collection

GC is reachability-based.

Roots include:

active pipeline runs
release manifests
deployment generations
user pins
development profiles
retention policies
compliance holds

Algorithm:

roots
  ↓
mark transitive closures
  ↓
unreachable objects
  ↓
grace period
  ↓
sweep

Never delete an object just because it is “old”.


24. Generations

Mutable environments point to immutable generations.

Example:

production
  generation 41 -> release digest A

production
  generation 42 -> release digest B

Rollback:

active generation 42 -> generation 41

No dependency re-resolution is required.


25. Project Manifest and Lock

Recommended:

forgeyard.ron   -> human-authored intent
forgeyard.lock  -> machine-maintained immutable resolution

The lock graph is authoritative for CI/release realization.


26. Manifest vs Lock

Manifest may say:

rust: (
    channel: "stable",
),

source: Git(
    url: "https://example.invalid/project.git",
    rev: Branch("main"),
),

Resolver produces immutable lock data:

rust stable
  -> rust 1.90.0
  -> manifest/content digests

branch main
  -> commit 4c920...
  -> tree digest blake3:...

Production builds never silently re-resolve main.


27. Lock Graph Model

#![allow(unused)]
fn main() {
pub struct LockGraph {
    pub schema: LockSchemaVersion,
    pub root: LockNodeId,
    pub nodes: BTreeMap<LockNodeId, LockedInput>,
}
}

Each locked input records:

source kind
original request
resolved immutable identity
content digest
dependency edges
fetch metadata
signature/trust metadata

28. Supported Locked Inputs

Resolvers may support:

Git
HTTP/HTTPS
OCI
Crates.io
npm
PyPI
Maven
NuGet
Go modules
Rust toolchains
Android SDK/NDK
JDKs
C/C++ toolchains
custom Forgeyard registry
local vendored sources

29. Git Locking

Unsafe production input:

branch = main
tag = latest

Resolved lock:

repository identity
commit SHA
tree/content digest
requested ref metadata

Forgeyard records both requested and resolved values for review.


30. HTTP Locking

Production HTTP inputs need:

URL
expected digest
optional expected size
optional upstream signature

Mutable URL bytes changing later cause digest verification failure.


31. Ecosystem Lockfiles

Forgeyard complements rather than replaces:

Cargo.lock
package-lock.json
pnpm-lock.yaml
yarn.lock
uv.lock
poetry.lock
go.sum
Gradle dependency locking

Forgeyard records the ecosystem lockfile digest and fetch closure.


32. Lock Workflow

forgeyard lock
forgeyard lock update
forgeyard lock update <input>
forgeyard lock diff

CI/release default:

forgeyard build --locked

Manifest-lock mismatch:

FAIL

not silent rewrite.


33. Semantic Lock Diff

PR review should show:

openssl 3.4.1 -> 3.5.0
source digest changed
2 transitive packages added
license changed
new vulnerability status

rather than only machine-generated textual noise.


34. Signed Lock Graphs

Enterprise policy may require an approved signed lock graph before production realization.

This turns dependency resolution into a reviewable supply-chain event.


35. Source Snapshots

A build receives an immutable source tree.

working tree
  ↓
include/exclude rules
  ↓
canonical source tree
  ↓
digest
  ↓
SourceTree store object

The builder does not need access to developer Git internals.


36. Dirty Trees

Local development may allow dirty source.

Identity includes:

base commit
dirty tree content digest

Release policy can deny dirty trees.


37. Two-Phase Network Model

Critical architecture:

RESOLVE/FETCH
   network may be allowed
        ↓
immutable store objects
        ↓
REALIZE/BUILD
   network denied

This removes the mutable internet from strict build inputs.


38. Three-Phase Release Model

High-assurance release:

resolve
  ↓
fetch and verify entire closure
  ↓
build completely offline

This is one of the strongest practical controls against hidden mutable dependencies.


39. Fixed-Output Fetches

Fetcher model:

URL/source
+
expected content digest
      ↓
fetch
      ↓
hash
      ↓
match?
  ├── no -> reject
  └── yes -> immutable store object

Remote server changes cannot silently affect build output.


40. Hermetic Build Environment

A strict builder can see:

declared source inputs
declared build dependencies
declared toolchains
controlled writable build directory
declared environment variables
explicit platform interfaces

It cannot see by default:

host /usr
host /opt
host home
host package caches
SSH agent
Docker socket
host credentials
random host executables

41. Linux Sandbox Architecture

Preferred layers:

namespaces
bubblewrap or equivalent
mount namespace
user namespace
PID namespace
network namespace
cgroup v2
seccomp
capability dropping
read-only store mounts
tmpfs/scratch build root

Optional eBPF improves enforcement/diagnostics.

eBPF is not required for core correctness.


42. Windows Hermeticity

Use Windows-native controls:

Job Objects
restricted tokens
ACL-isolated workspace
controlled environment
explicit toolchain paths
firewall/network rules
AppContainer where suitable
VM runners for stronger isolation

Do not emulate Linux namespace assumptions.


43. macOS Hermeticity

Use:

ephemeral workspaces
explicit Xcode/SDK identity
controlled environment
sandbox capabilities where practical
network policy
dedicated runner identities

Platform SDKs that cannot live in Forgeyard store become explicit platform-contract inputs.


44. VM-Based High-Assurance Builder

derivation
  ↓
ephemeral VM image by identity
  ↓
Forgeyard agent
  ↓
read-only input closure
  ↓
build
  ↓
export outputs
  ↓
destroy VM

Suitable for high-trust release pools.


45. Deterministic Environment Synthesis

Forgeyard creates the build environment instead of inheriting it.

Example default:

LANG=C.UTF-8
LC_ALL=C.UTF-8
TZ=UTC
HOME=/build/home
PATH=<declared store binaries only>
SOURCE_DATE_EPOCH=<stable value>

Host environment variables are scrubbed unless explicitly allowed.


46. Time

Wall-clock time is an impurity.

Use a stable build epoch when possible.

Example:

SOURCE_DATE_EPOCH = source commit timestamp

or policy-defined epoch.

If real current time is genuinely required, declare impurity and reduce reproducibility status.


47. Randomness

Build randomness should be:

disabled
or
seeded deterministically
or
explicitly declared

Possible deterministic seed:

FORGEYARD_BUILD_SEED = H(DerivationId)

Do not generate release signing keys during reproducible build realization.


48. Locale and Timezone

Use controlled values:

LANG=C.UTF-8
LC_ALL=C.UTF-8
TZ=UTC

unless the derivation declares alternatives.


49. Filesystem Ordering

Canonical store tree entries are sorted.

Builders should not rely on OS directory-enumeration order.

Reproducers can vary physical filesystems to detect hidden assumptions.


50. UID/GID and Modes

Package output should not inherit arbitrary host ownership.

Canonical package policy may use:

uid = 0
gid = 0

or package-format-specific values.

Normalize file modes where semantics permit.


51. Timestamps

Canonical package writers normalize:

mtime
archive timestamps
ZIP timestamps
tar headers
metadata fields

according to reproducibility policy.


52. Absolute Build Paths

Use stable virtual paths:

/build/source
/build/work

and compiler remapping where available.

Rust example:

--remap-path-prefix

C/C++ examples:

-ffile-prefix-map
-fdebug-prefix-map

53. Toolchains as Immutable Inputs

Toolchains are store objects or explicit platform contracts.

Rust toolchain may include:

rustc
cargo
rust-std targets
rustfmt
clippy

A builder must not accidentally use arbitrary host cargo.


54. Toolchain Bootstrapping

Every system eventually has a bootstrap trust root.

Forgeyard records:

bootstrap source
digest
signature if available
provenance if available

Do not claim infinite purity.

Expose the trusted bootstrap boundary.


55. Platform Contract

Some inputs are platform services rather than store objects.

#![allow(unused)]
fn main() {
pub struct PlatformContract {
    pub os: Os,
    pub arch: Architecture,
    pub kernel_abi: Option<AbiConstraint>,
    pub cpu_features: CpuFeatureSet,
    pub system_sdk: Option<SdkIdentity>,
}
}

Scheduler must satisfy the contract.


56. Build / Host / Target Platforms

Use distinct types:

#![allow(unused)]
fn main() {
pub struct BuildPlatform(...);
pub struct HostPlatform(...);
pub struct TargetPlatform(...);
}

Example cross compile:

build  = x86_64-linux
host   = x86_64-linux
target = aarch64-linux

57. Multi-Platform Derivations

Same package recipe produces different platform derivations:

app-x86_64-linux
app-aarch64-linux
app-x86_64-windows
app-aarch64-darwin

Their output digests are expected to differ.


58. Universal Release Index

A release groups immutable platform artifacts:

Forgeyard 1.0.0
├── linux-x86_64  -> digest A
├── linux-aarch64 -> digest B
├── windows-x86_64 -> digest C
└── macos-aarch64 -> digest D

The release index itself is immutable and signed.


59. Realizer

#![allow(unused)]
fn main() {
#[async_trait]
pub trait Realizer {
    async fn realize(
        &self,
        derivation: &ResolvedDerivation,
        context: RealizationContext,
    ) -> Result<Realization>;
}
}

Responsibilities:

  1. validate schema;
  2. verify lock graph;
  3. verify/substitute inputs;
  4. prepare sandbox;
  5. synthesize environment;
  6. execute builder;
  7. validate output contract;
  8. normalize outputs;
  9. hash outputs;
  10. atomically commit store objects;
  11. emit provenance.

60. Build Phases

Generic model:

fetch
unpack
patch
configure
build
check
install
fixup
package

Not every ecosystem uses all phases.

Structured phases improve policy and diagnostics.


61. Output Contract

Example:

outputs: [
    (
        name: "server",
        kind: Executable,
        path: "bin/server",
    ),
    (
        name: "assets",
        kind: Directory,
        path: "share/assets",
    ),
]

Undeclared outputs may be ignored, warned, or rejected according to policy.


62. Canonical Output Tree

Canonical entry representation includes:

normalized path name
entry type
content digest
normalized executable bit
symlink target

It excludes irrelevant host metadata such as inode numbers.


63. Atomic Store Commit

temporary output
  ↓
validate
  ↓
normalize
  ↓
hash
  ↓
fsync where needed
  ↓
atomic commit

Failed builds never leave partial data that looks valid.


64. Read-Only Inputs

Store inputs are mounted/read as immutable.

Possible optimizations:

read-only bind mounts
reflinks
hard links where safe
overlay
CAS-backed filesystem

Builder never receives write permission to input objects.


65. Writable Workspace

Each realization receives a separate writable build area.

Outputs are imported into the store only after successful validation.


66. Reproducibility Verification

Derivation D
  ├── Runner A -> Output X
  └── Runner B -> Output Y

If:

X == Y

the derivation is reproduced.

If:

X != Y

Forgeyard records mismatch and quarantines according to policy.


67. Reproducer Diversity

Policy may require:

different runner IDs
different physical hosts
different runner pools
different regions

For high assurance, independent build infrastructure is preferable.


68. Reproducibility Policy

#![allow(unused)]
fn main() {
pub struct ReproducibilityPolicy {
    pub required_rebuilds: u8,
    pub require_distinct_hosts: bool,
    pub require_distinct_pools: bool,
    pub comparison: ComparisonMode,
}

pub enum ComparisonMode {
    BitForBit,
    NormalizedTree,
    Semantic,
}
}

Prefer bit-for-bit for release binaries when achievable.


69. Mismatch Diagnostics

Pipeline:

output digest mismatch
  ↓
tree diff
  ↓
changed file list
  ↓
metadata diff
  ↓
binary/package classifier

Likely causes:

timestamp
absolute path
archive order
random UUID
locale
toolchain nondeterminism
filesystem ordering
network-derived input
signature data

70. Reproduction Quarantine

Mismatch:

artifact state = Quarantined

Default restrictions:

no stable promotion
no production deployment
no "reproducible" attestation

until resolved or explicitly waived by policy.


71. Signed Outputs and Reproducibility

Split:

reproducible unsigned artifact
    ↓
reproduction verified
    ↓
signing/notarization step
    ↓
distribution artifact

Signature timestamps or external notarization should not contaminate reproducibility of the core build.


72. Build Provenance

#![allow(unused)]
fn main() {
pub struct BuildProvenance {
    pub derivation_id: DerivationId,
    pub output_digests: Vec<OutputDigest>,
    pub runner_identity: RunnerIdentity,
    pub platform_identity: PlatformIdentity,
    pub lock_graph_digest: LockGraphId,
    pub source_digest: SourceDigest,
    pub toolchain_digests: Vec<Digest>,
    pub sandbox_policy_digest: Digest,
}
}

Timing can be included as provenance metadata without being a build input.


73. Binary Substitution

Before build:

Derivation D
  ↓
query trusted substituters
  ├── found
  │    ↓
  │  fetch realization
  │    ↓
  │  verify signatures/digests
  │    ↓
  │  use
  │
  └── miss
       ↓
      build

This prevents purity from forcing a rebuild of every dependency on every machine.


74. Substituter Interface

#![allow(unused)]
fn main() {
#[async_trait]
pub trait Substituter {
    async fn find(
        &self,
        derivation: &DerivationId,
    ) -> Result<Vec<RemoteRealization>>;

    async fn fetch_object(
        &self,
        digest: &Digest,
    ) -> Result<ObjectReader>;
}
}

Transports:

HTTP
S3/object store
Forgeyard QUIC
Iroh peer distribution

75. Cache Trust

Remote mapping:

D -> O

must be authenticated.

Accept based on policy such as:

trusted signed realization metadata
verified content-addressed output
trusted provenance
local reproduction

Never trust a cache solely because it is reachable.


76. Cache Poisoning Resistance

Every downloaded object is hashed before commit.

Substitution metadata is signed or otherwise authenticated.

A malicious mirror cannot silently change an object under the same digest.


77. Store vs Cache

Keep distinct:

functional store:
  immutable objects and closure relationships

action/build cache:
  derivation/action key -> realizations

local mutable accelerator:
  disposable compiler/intermediate cache

A cache may be evicted.

A pinned release store object may not disappear merely because of cache pressure.


78. Scheduler Integration

Scheduler filters by hard constraints:

build platform
target support
sandbox capability
trust tier
memory/CPU/disk
required SDK
reproducer separation policy

Then scores:

input closure locality
toolchain locality
queue delay
resource headroom
cache affinity
network cost

79. Closure-Aware Scheduling

Example:

Runner A already has 28 GiB of required 30 GiB closure
Runner B has 1 GiB

Prefer A if all hard constraints match.

This can make functional builds highly efficient.


80. Realization Lease

Lease includes:

DerivationId
lock graph identity
expected input closure
sandbox policy
network policy
platform contract

Runner validates before acceptance.


81. Preflight

Before expensive realization:

verify platform
verify sandbox capability
verify disk capacity
verify object availability
verify network policy
verify output capacity

Only then accept execution lease.


82. Concurrent Realization

Multiple machines may realize same derivation.

Correctness must tolerate this.

The store deduplicates identical outputs by digest.

Scheduler may single-flight ordinary builds while intentionally scheduling additional reproducer runs.


83. Developer Environment

Use the same lock/derivation model locally:

forgeyard dev

Flow:

forgeyard.ron
+
forgeyard.lock
   ↓
DevEnvironment derivation
   ↓
toolchain/dependency substitution
   ↓
controlled shell

This narrows developer-vs-CI drift.


84. Dev Shell Modes

forgeyard dev
forgeyard dev --pure

Pure mode scrubs host environment except declared allowlist.

Convenient non-pure mode remains visibly classified as less reproducible.


85. Minimal Runner Philosophy

Do not depend on giant mutable CI images such as:

ubuntu-runner-with-everything-installed

Prefer:

minimal Forgeyard runner
+
sandbox runtime
+
content-addressed toolchains
+
project closure

This dramatically reduces runner-image drift.


86. Incremental Compilation

Mutable incremental caches are useful locally but risky as release inputs.

Policy:

development -> incremental allowed
release -> clean realization by default

Any mutable acceleration cache is disposable and not part of trusted output identity unless explicitly modeled.


87. Cargo Integration

Rust derivation inputs include:

source tree
Cargo.toml
Cargo.lock
Rust toolchain
target triple
native library closure
declared environment
build.rs behavior

Use:

cargo build --locked

Strict build runs with registry/git sources already fetched.


88. Cargo Build Scripts

build.rs is arbitrary code.

It runs in the hermetic sandbox.

Attempts to:

read unexpected /usr paths
download packages
invoke random host tools

are blocked or reported.


89. C/C++ Integration

Explicitly model:

compiler
linker
sysroot
headers
libraries
cmake/meson/ninja
pkg-config metadata
target flags

Do not depend on /usr/include unless platform policy explicitly says so.


90. Java/Gradle Integration

Lock:

JDK
Gradle wrapper
plugins
Maven/Gradle dependencies
repository source identities

Prefetch then build offline where ecosystem permits.


91. Node Integration

Require an ecosystem lockfile for production.

Prefetch packages.

Run build with network denied.


92. Python Integration

Lock:

interpreter
wheels/sdists
native build dependencies
compiler inputs

Do not resolve arbitrary dependency ranges during realization.


93. Android Integration

Lock or explicitly identify:

JDK
Gradle
Android SDK platform
build-tools
NDK
CMake if used
dependency closure

The Android SDK must not be merely “whatever exists on the runner”.


94. Apple Integration

Explicitly identify:

Xcode version
SDK build version
deployment target
architecture

Where licensing/technical constraints prevent packaging Xcode into Forgeyard store, treat it as a trusted platform input and record it in provenance.


95. Windows Integration

Explicit:

MSVC toolset
Windows SDK
linker
runtime
PowerShell version where relevant

Do not use arbitrary installed Visual Studio state.


96. Runtime Linkage Verification

After build, inspect native binaries.

Linux:

ELF interpreter
DT_NEEDED
RPATH/RUNPATH

Windows:

PE imports

macOS:

Mach-O load commands

Every dependency must belong to runtime closure or approved platform ABI.


97. Clean-Machine Verification

Command:

forgeyard package verify-clean

Runs package in environment containing only:

declared runtime closure
+
platform contract

This directly tests the “works outside the builder” guarantee.


98. Deterministic Packaging

Packaging is another derivation.

Input:

immutable realized output

Output:

immutable package digest

Package writer must use canonical ordering/metadata.


99. Supported Package Formats

Adapters may produce:

tar.zst
zip
.deb
.rpm
APK
AAB
MSI
MSIX
OCI image
WASM bundle
static-site bundle
Forgeyard native bundle

Each adapter documents reproducibility limitations.


100. Deterministic TAR

Canonical policy:

sorted paths
normalized UID/GID
normalized uname/gname
fixed mtime
stable PAX fields
normalized modes
deterministic compressor settings

101. Deterministic ZIP

Normalize:

entry ordering
timestamps
permissions
extra fields
compression parameters

102. OCI Images

Release derivations must not depend on mutable base tags.

Bad:

ubuntu:latest

Acceptable:

base image digest = sha256:...

Better where feasible:

construct root filesystem directly from immutable runtime closure

103. Forgeyard Native Bundle

Potential extension:

.fypkg

Contains or references:

package manifest
root object digest
runtime closure
SBOM
provenance
signatures

Modes:

thin -> references remote immutable objects
fat  -> embeds full closure for offline transfer

104. Package Manifest

#![allow(unused)]
fn main() {
pub struct PackageManifest {
    pub name: PackageName,
    pub version: PackageVersion,
    pub target: TargetPlatform,
    pub root: StoreRef,
    pub runtime_closure: Vec<StoreRef>,
    pub sbom: Option<StoreRef>,
    pub provenance: StoreRef,
}
}

Manifest itself is immutable and content-addressed.


105. Build Once, Promote Many

Hard invariant:

BUILD ONCE
VERIFY
PROMOTE SAME BYTES

Never:

build dev
rebuild staging
rebuild production

Instead:

build digest X
test X
reproduce X
approve X
stage X
deploy X

106. Distribution Channels

Channels are mutable references to immutable release manifests.

Example:

nightly
beta
stable
production
stable -> ReleaseDigest A

Promotion changes reference to B.

A and B remain unchanged.


107. Environment Promotion

artifact X
  ├── development
  ├── test
  ├── staging
  └── production

All use identical software bytes.

Runtime config/secrets are injected separately.


108. Runtime Configuration Separation

Package:

immutable software

Deployment supplies:

environment configuration
secrets
service endpoints
deployment policy

Do not rebuild binaries merely to change production configuration.


109. Promotion State Machine

Built
  ↓
Tested
  ↓
ReproducibilityVerified
  ↓
SecurityApproved
  ↓
Signed
  ↓
Published
  ↓
Promoted

All transitions reference immutable artifact/release digests.


110. Rollback

Rollback changes generation/reference:

production -> release B

to:

production -> release A

No dependency solving or rebuild in the rollback critical path.


111. Distribution Architecture

Origin immutable store
   ├── region mirror A
   ├── region mirror B
   ├── site CAS
   └── iroh peer cache

All transports deliver bytes identified by digest.

Trust derives from digest/signature/provenance, not mirror hostname alone.


112. Iroh P2P Role

Iroh is a transfer accelerator.

need digest X
  ↓
discover peer with X
  ↓
fetch
  ↓
verify X

It is not the source of truth for package identity.


113. Air-Gapped Build

forgeyard bundle inputs

creates a closure containing:

lock graph
source objects
toolchains
dependencies
derivation objects
signatures

Disconnected builder:

forgeyard build --offline

114. Air-Gapped Distribution

Export release:

release manifest
runtime closure
SBOM
provenance
signatures

into a portable Forgeyard bundle.

Offline importer verifies before activation.


115. Source Mirrors

Resolver can mirror immutable upstream inputs into organization storage.

Release policy may require:

production builds fetch only from approved internal mirrors

The lock still uses content identity.


116. Input Trust

#![allow(unused)]
fn main() {
pub enum InputTrust {
    Unverified,
    DigestVerified,
    SignatureVerified,
    OrganizationApproved,
    Revoked,
}
}

Immutability does not imply safety.

A malicious package can be perfectly immutable.

Trust and identity remain separate.


117. Dependency Policy

Before realization:

lock graph
  ↓
source trust
  ↓
license policy
  ↓
vulnerability policy
  ↓
derivation

Policy evidence is attached to provenance.


118. SBOM From the Resolved Closure

Prefer generating SBOM from known locked dependency metadata and closure.

Binary analysis can supplement it.

This gives Forgeyard strong knowledge of:

name
version
source
digest
dependency relationship

119. Historical Rebuildability

A historical release is rebuildable if Forgeyard preserves:

manifest
lock graph
source closure
toolchain closure
derivation graph
platform contract

Upstream registries can disappear without destroying build identity.


120. Long-Term Archive

For important releases preserve:

source closure
toolchain closure
derivations
lock graph
release objects
SBOM
provenance
signatures

Cold object storage can retain these.


121. Persistence Model

Suggested metadata tables:

derivations
derivation_inputs
realizations
realization_outputs
reproduction_attempts
reproduction_diffs
store_objects
store_references
store_roots
lock_graphs
profiles
generations
promotions
substituter_records

Large bytes remain in CAS.


122. Store Roots and Profiles

Profiles are mutable references over immutable objects.

Example:

profile: developer
generation: 17
closure: digest X

Updating tools creates generation 18.

Rollback restores 17.


123. Impurity Auditing

Command:

forgeyard build --audit-impurity

Diagnostic mode observes:

unexpected filesystem access
unexpected process lookup
unexpected network access
host environment leakage

On Linux, possible mechanisms include sandbox-denial logs and optional eBPF/fanotify/ptrace-style diagnostics.


124. Purity Migration

Projects can migrate gradually:

Audit
  ↓
Warn
  ↓
Enforce filesystem isolation
  ↓
Enforce network isolation
  ↓
Reproduce release outputs

This makes the architecture practical for existing codebases.


125. Example Hermeticity Violation

Hermeticity violation

process:
  cmake

attempted path:
  /usr/bin/pkg-config

reason:
  undeclared executable

suggestion:
  declare pkg-config as a build input

126. Example Network Violation

Hermeticity violation

process:
  cargo

attempted:
  crates.io:443

likely cause:
  source closure was not prefetched

suggestion:
  forgeyard fetch --all

127. Controlled Impurity

Some builds need exceptions.

#![allow(unused)]
fn main() {
pub enum Impurity {
    Network(NetworkAllowance),
    Clock(ClockAllowance),
    HostPath(ApprovedHostPath),
    Hardware(DeviceRequirement),
}
}

Controlled impurity is better than hidden impurity.

Release policy can reject it.


128. Secrets

Default:

secrets in reproducible build = denied

Prefer:

reproducible build
  ↓
verified output
  ↓
signing step with secret

If a secret genuinely affects output, reproducibility semantics must state that explicitly.


129. Private Dependencies

Fetcher may use credentials.

Credential is not build identity.

Fetched source digest is.

Private source object becomes an authenticated immutable store object.


130. Multi-Tenant Store

Authorization remains tenant-scoped even if physical storage internally deduplicates identical content.

Do not expose cross-tenant existence information if policy considers that a side channel.


131. Store Corruption Repair

If local digest verification fails:

mark local copy corrupt
  ↓
fetch trusted replica
  ↓
verify digest
  ↓
replace physical copy

Logical object identity is unchanged.


132. Replication Policy

Object state may be:

local-only
site-replicated
region-replicated
durable-object-store
archived

Release gate can require minimum durability before promotion.


133. Reproducibility vs Availability

These are separate dimensions.

A reproducible artifact with zero durable replicas is still operationally unsafe.

Policy can require:

reproduction_count >= 1
and
durable_replica_count >= 2

134. Rebuild Campaigns

If toolchain digest X is compromised:

reverse dependency query
  ↓
all derivations using X
  ↓
resolve replacement Y
  ↓
create new derivations
  ↓
rebuild

No need to inspect which machines happened to have toolchain X installed.


135. Revocation

Immutable objects are never silently replaced.

Instead:

digest X -> Revoked

Policy blocks future trusted use.

Historical builds remain auditable.


136. Runtime Dependency Verification

Package validator ensures all runtime references are either:

in declared closure
or
approved platform ABI

A package that builds but depends on random machine libraries fails validation before release.


137. Integration Tests With Services

If tests require a database:

PostgreSQL package/image digest
schema fixture digest
seed digest

Service is ephemeral; inputs are immutable.

Live third-party API tests are classified non-hermetic and attached as evidence rather than affecting package identity.


138. Device Testing

Separate:

build derivation

from:

device test action

A physical Android/iOS device is not part of a reproducible binary derivation.

Its test result is release evidence.


139. Hardware-Specific Builds

If compilation uses:

-march=native

CPU feature set becomes a declared platform input.

Prefer explicit target features for portable release builds.


140. Reproducibility Stress Testing

Optional verifier can vary:

physical host
filesystem
wall-clock time
host timezone
host environment
physical build directory

while keeping declared derivation inputs identical.

Differences expose hidden impurity.


141. File-System Diversity

Example:

ext4
xfs
btrfs

If output changes, project depends on undeclared filesystem behavior.

This is useful for high-assurance release verification.


142. Deterministic Compression

Package derivation fixes:

algorithm
compression level
dictionary
thread behavior where relevant
metadata

Parallel compression must be tested for deterministic output.


143. Release Manifest

#![allow(unused)]
fn main() {
pub struct ReleaseManifest {
    pub version: Version,
    pub artifacts: BTreeMap<TargetPlatform, PackageDigest>,
    pub sboms: BTreeMap<TargetPlatform, Digest>,
    pub provenance: BTreeMap<TargetPlatform, Digest>,
}
}

Sign the manifest digest.


144. Stable Download URLs

Human URL:

/releases/1.0.0/linux-x86_64

resolves to immutable digest object.

Also expose digest-addressed retrieval for verification.


145. Update System

Client updater:

fetch signed channel/release metadata
  ↓
verify signature
  ↓
select platform artifact
  ↓
verify digest
  ↓
download
  ↓
atomic install/switch

Never trust filename/version string alone.


146. Native Forgeyard CLI

Recommended commands:

forgeyard lock
forgeyard lock update
forgeyard lock diff
forgeyard fetch
forgeyard derive
forgeyard realize
forgeyard build
forgeyard build --locked
forgeyard build --offline
forgeyard build --audit-impurity
forgeyard reproduce
forgeyard diff
forgeyard dev
forgeyard store closure
forgeyard store graph
forgeyard store why-depends
forgeyard store verify
forgeyard store gc
forgeyard substitute
forgeyard package
forgeyard package verify-clean
forgeyard promote
forgeyard rollback
forgeyard bundle inputs
forgeyard bundle release

147. forgeyard derive

Displays build identity without executing.

Example:

Derivation: blake3:...
Source: blake3:...
Lock: blake3:...
Toolchains:
  rustc -> blake3:...
Build platform: x86_64-linux
Target: x86_64-linux
Network: denied
Hermeticity: strict

148. forgeyard explain rebuild

Shows why identity changed.

Example:

Previous derivation:
  blake3:A

Current:
  blake3:B

Reason:
  Cargo.lock digest changed

Old:
  sha256:X

New:
  sha256:Y

149. Dioxus UI

New views:

Derivation detail
Lock graph
Input closure
Store browser
Reproducibility status
Reproducer comparison
Hermeticity violations
Package closure
Promotion history
Distribution mirrors

150. Reproducibility UI

Show:

Derivation ID
Source digest
Lock digest
Input count / bytes
Toolchains
Build platform
Sandbox policy
Network policy
Primary output digest
Independent rebuilds
Match/mismatch
SBOM
Provenance
Signature
Promotion state

151. “Why Rebuild?” UI

When cache miss occurs, explain exactly which derivation inputs changed.

This prevents functional package management from becoming opaque.


152. Policy Extensions

New policy controls:

require_lock_file
require_clean_source
deny_build_network
require_hermetic
require_content_addressed_output
require_reproducibility_count
require_distinct_rebuilder
require_signed_substituter
require_sbom
require_provenance
deny_untrusted_input
deny_revoked_input
deny_promotion_rebuild

153. Failure Types

#![allow(unused)]
fn main() {
pub enum FunctionalBuildError {
    LockMismatch,
    MissingInput,
    DigestMismatch,
    UntrustedInput,
    HermeticityViolation,
    PlatformMismatch,
    BuildFailure,
    OutputContractViolation,
    ReproductionMismatch,
    SubstitutionTrustFailure,
    StoreCorruption,
}
}

154. Idempotency

Natural identities make operations idempotent:

resolve(manifest_digest)
fetch(object_digest)
realize(derivation_id)
package(package_derivation_id)
promote(release_digest, channel)

Retries do not create semantically different outputs.


155. Crash Safety

Temporary realization is never considered valid until atomic commit.

After agent restart:

reconcile lease
scan temporary workspace
verify committed objects
remove abandoned temporary data after grace period

156. Cancellation

Cancellation:

stop builder process tree
discard uncommitted temporary output
retain already committed immutable inputs

No partial store object is published.


157. Backpressure

Bound:

fetch concurrency
CAS transfers
decompression
store commit concurrency
build logs
reproduction queue

Functional store does not justify unbounded I/O.


158. Performance Strategy

Optimize in this order:

avoid work
substitute
reuse closure
schedule for locality
deduplicate
reflink/mount
parallelize dependency DAG
compress transport
then optimize kernel I/O

Do not weaken hermeticity merely for benchmark speed.


159. Multi-Level Store

L0 metadata cache
L1 runner SSD
L2 site/LAN CAS
L3 regional object storage
L4 archival storage

All expose the same immutable logical identities.


160. Predictive Prefetch

Existing Forgeyard predictive caching can prefetch likely closures.

Incorrect prediction may waste bandwidth but cannot alter build correctness.


161. Metrics

Suggested:

forgeyard_derivation_realizations_total
forgeyard_substitution_hits_total
forgeyard_substitution_misses_total
forgeyard_store_bytes
forgeyard_store_gc_bytes_total
forgeyard_reproduction_matches_total
forgeyard_reproduction_mismatches_total
forgeyard_hermeticity_violations_total
forgeyard_lock_updates_total
forgeyard_input_fetch_bytes_total
forgeyard_package_promotions_total

162. Tracing

Trace lifecycle:

resolve
fetch
verify
compile derivation
query substituter
materialize closure
create sandbox
build
normalize
hash
commit
reproduce
package
sign
promote

163. Audit Events

Examples:

LockUpdated
LockApproved
UntrustedInputAllowed
ImpurityAllowed
ReproductionMismatch
SubstituterTrustChanged
StoreRootRemoved
GarbageCollectionRun
ReleasePromoted
ReleaseRolledBack

164. Reproduction Mismatch Is Security-Relevant

A mismatch may indicate:

  • accidental nondeterminism;
  • hidden host input;
  • compromised runner;
  • malicious cache;
  • compromised compiler/toolchain.

Production release mismatches should create high-severity audit/security events.


165. Workspace Architecture

crates/
├── forgeyard-derivation/
│   └── src/
│       ├── canonical.rs
│       ├── digest.rs
│       ├── graph.rs
│       ├── model.rs
│       └── validate.rs
│
├── forgeyard-lock/
│   └── src/
│       ├── graph.rs
│       ├── diff.rs
│       ├── git.rs
│       ├── http.rs
│       ├── cargo.rs
│       └── toolchain.rs
│
├── forgeyard-functional-store/
│   └── src/
│       ├── object.rs
│       ├── closure.rs
│       ├── root.rs
│       ├── generation.rs
│       ├── gc.rs
│       └── verify.rs
│
├── forgeyard-hermetic/
│   └── src/
│       ├── environment.rs
│       ├── network.rs
│       ├── linux.rs
│       ├── windows.rs
│       ├── macos.rs
│       └── impurity.rs
│
├── forgeyard-realizer/
│   └── src/
│       ├── phases.rs
│       ├── substitute.rs
│       ├── materialize.rs
│       ├── output.rs
│       └── commit.rs
│
├── forgeyard-reproducibility/
│   └── src/
│       ├── compare.rs
│       ├── scheduler.rs
│       ├── diagnostics.rs
│       └── attestation.rs
│
├── forgeyard-package/
│   └── src/
│       ├── manifest.rs
│       ├── tar.rs
│       ├── zip.rs
│       ├── deb.rs
│       ├── oci.rs
│       └── native_bundle.rs
│
└── forgeyard-distribution/
    └── src/
        ├── release.rs
        ├── channel.rs
        ├── promotion.rs
        ├── mirror.rs
        ├── update.rs
        └── airgap.rs

166. Dependency Direction

model/digest
   ↓
lock
   ↓
derivation
   ↓
functional-store
   ↓
hermetic
   ↓
realizer
   ↓
reproducibility
   ↓
package
   ↓
distribution

Cross-cutting integrations:

forgeyard-cas
forgeyard-scheduler
forgeyard-runner
forgeyard-policy
forgeyard-signing
forgeyard-provenance
forgeyard-telemetry

167. Key Traits

Input resolver

#![allow(unused)]
fn main() {
#[async_trait]
pub trait InputResolver {
    type Request;
    type Locked;

    async fn resolve(
        &self,
        request: &Self::Request,
        policy: &ResolutionPolicy,
    ) -> Result<Self::Locked, ResolveError>;
}
}

Functional store

#![allow(unused)]
fn main() {
#[async_trait]
pub trait FunctionalStore {
    async fn has(&self, digest: &Digest) -> Result<bool>;
    async fn open(&self, digest: &Digest) -> Result<ObjectReader>;
    async fn closure(&self, root: &StoreObjectId) -> Result<Closure>;
    async fn add_root(&self, root: RootRef) -> Result<()>;
}
}

Realizer

#![allow(unused)]
fn main() {
#[async_trait]
pub trait Realizer {
    async fn realize(
        &self,
        derivation: &ResolvedDerivation,
        context: &RealizationContext,
    ) -> Result<Realization>;
}
}

Substituter

#![allow(unused)]
fn main() {
#[async_trait]
pub trait Substituter {
    async fn find(
        &self,
        derivation: &DerivationId,
    ) -> Result<Vec<RemoteRealization>>;

    async fn fetch(
        &self,
        digest: &Digest,
    ) -> Result<ObjectReader>;
}
}

168. Native Protocol Additions

QUIC/Postcard messages may include:

QueryRealization
RealizationAvailable
RealizationMissing
RealizeDerivation
InputClosureManifest
StoreObjectNeeded
StoreObjectAvailable
RealizationStarted
RealizationCompleted
ReproductionRequest
ReproductionResult
HermeticityViolation

Bulk content remains streamed through CAS/data-plane protocols.


169. API Additions

Potential endpoints:

/api/v1/locks
/api/v1/derivations
/api/v1/realizations
/api/v1/store
/api/v1/reproducibility
/api/v1/substituters
/api/v1/packages
/api/v1/releases
/api/v1/promotions

170. Standalone Deployment

Forgeyard local
  ├── resolver
  ├── lock graph
  ├── derivation compiler
  ├── local scheduler
  ├── hermetic realizer
  ├── local immutable store
  ├── package builder
  └── local distribution/export

No remote server required.

This preserves Forgeyard’s local-first model.


171. Distributed Deployment

                       Forgeyard daemon
               ┌──────────────────────────┐
               │ Derivation / Lock /      │
               │ Scheduler / Policy       │
               └────────────┬─────────────┘
                            │
                  QUIC + Postcard
                            │
         ┌──────────────────┼──────────────────┐
         ▼                  ▼                  ▼
   Hermetic Runner A   Hermetic Runner B   Rebuilder Pool
         │                  │                  │
         └────────┬─────────┴─────────┬────────┘
                  ▼                   ▼
              Site CAS           Durable CAS

172. Enterprise Additions

PostgreSQL/Neon metadata
OIDC/RBAC
multi-region CAS
signed substituters
independent rebuilder pools
release policy
retention/compliance
transparency log integration
air-gapped mirrors

The build semantics do not change.


173. Nix Interoperability

Forgeyard can optionally support:

Nix input resolver
Nix store import/export
Nix derivation adapter
Nix-based Linux toolchain provider

But native Forgeyard derivations remain independent.


174. Why Forgeyard Should Not Simply Require Nix

Forgeyard targets:

Linux
Windows
macOS
Android
possibly iOS build orchestration
WASM/web
distributed runner fleets
native Dioxus UI
Forgeyard provenance/release policy

Nix interoperability is valuable, but mandatory Nix installation would unnecessarily bind Forgeyard’s entire execution architecture to one package manager/runtime.


175. Lessons Adopted From Functional Package Management

Forgeyard should adopt:

immutable package values
derivations
dependency closures
locked inputs
cryptographic store identity
substitution
generation switching
reachability GC

Forgeyard extends these with:

independent output hashing
reproducibility verification
cross-platform execution
runner scheduling
release promotion
CI policy
supply-chain attestations

176. Implementation Phase 1 — Identity Foundation

Implement:

Digest types
CanonicalDerivationV1
StoreObjectV1
OutputTreeV1
LockGraphV1

Exit criterion:

same canonical logical object hashes identically across supported implementations

177. Phase 2 — Immutable Local Store

Implement:

CAS-backed object commit
closure graph
roots
generations
verification
GC

Exit:

corrupt bytes are detected and live roots survive GC.


178. Phase 3 — Locking and Fetching

Start with:

Git
HTTP
Cargo/Rust toolchain

Exit:

a Rust project can build offline after forgeyard fetch.


179. Phase 4 — Linux Hermetic Realizer

Implement:

read-only inputs
clean environment
network denial
namespace sandbox
stable build root
output canonicalization

Exit:

removing host-installed compilers/libraries does not affect the reference build.


180. Phase 5 — Substitution

Implement:

realization index
signed cache metadata
remote CAS transfer
trust policy

Exit:

fresh runner can obtain full dependency closure without rebuilding it.


181. Phase 6 — Reproducibility Verification

Implement:

independent rebuild scheduling
content comparison
tree diff
quarantine
attestation

Exit:

intentionally timestamp-dependent sample build is detected as non-reproducible.


182. Phase 7 — Deterministic Packaging

Implement:

tar.zst
zip
Linux package adapter
package manifest
release manifest

Exit:

two independent packaging runs match for reference projects.


183. Phase 8 — Promotion and Distribution

Implement:

channels
signed release metadata
mirror distribution
build-once-promote
rollback generations

Exit:

same digest moves from test to staging to production.


184. Phase 9 — Windows

Implement:

Windows toolchain locking
controlled environment
sandbox/isolation adapter
runtime closure validation

185. Phase 10 — Android

Implement:

JDK lock
Gradle lock
SDK/NDK identity
deterministic unsigned package pipeline
separate signing

186. Phase 11 — macOS / Apple

Implement:

Xcode/SDK platform identity
dedicated macOS runners
controlled workspace
reproducible unsigned core where possible
separate signing/notarization

187. Phase 12 — Independent Enterprise Rebuilders

Implement:

separate rebuilder pools
cross-region verification
required reproduction count
transparency integration

188. Acceptance Tests

Host compiler test

Remove host compiler.

Build still succeeds.

PATH test

Randomize host PATH.

Output unchanged.

Timezone test

Runner A UTC, Runner B IST.

Output unchanged.

Wall-clock test

Build on another day.

Output unchanged.

Offline test

Build succeeds after fetch with network disabled.

Mutable URL test

Remote bytes change.

Digest verification fails.

Clean-machine test

Same derivation succeeds on two clean machines.

Timestamp injection test

Reproducer detects mismatch.

Cache poisoning test

Forged realization/object rejected.

Promotion test

Production receives exact staging digest.


189. Production Readiness Gates

Do not call this subsystem production-ready until:

canonical hash test vectors exist
lock schema has compatibility policy
offline builds work
network denial is enforced
host-state leakage tests pass
substitution trust works
store corruption detection works
GC cannot remove live releases
independent reproduction works
deterministic package tests pass
rollback works through immutable generations

190. Architectural Invariants

  1. Production realization never resolves mutable dependency versions.
  2. Committed store objects are immutable.
  3. Builders cannot modify input store objects.
  4. Strict builds cannot see undeclared host filesystem state.
  5. Strict builds cannot access network unless policy explicitly permits it.
  6. Every realized output is content-hashed.
  7. Forgeyard never hides multiple different outputs for the same derivation.
  8. Promotion never rebuilds an artifact.
  9. Secrets do not affect reproducible build outputs by default.
  10. Remote substitutions are verified before trust.
  11. Runtime dependencies are closure-validated.
  12. Lock graph used by a release is preserved by digest.
  13. CI runner image is not the project dependency environment.
  14. P2P improves transport but never defines artifact identity.
  15. Kubernetes/containers are execution adapters, not reproducibility semantics.

191. Recommended Release Policy

(
    release: (
        locked_inputs: Required,
        dirty_source: Denied,
        build_network: Denied,
        hermeticity: Required,

        reproducibility: (
            rebuilds: 1,
            distinct_host: true,
            comparison: BitForBit,
        ),

        runtime_closure_validation: Required,
        sbom: Required,
        provenance: Required,
        signing: Required,
        promotion_rebuild: Denied,
    ),
)

192. Example Package Derivation

(
    name: "forgeyard",
    version: "1.0.0",

    source: Locked("project-source"),

    toolchains: [
        Locked("rust-toolchain"),
    ],

    build_inputs: [
        Locked("openssl"),
    ],

    builder: Cargo(
        args: [
            "build",
            "--release",
            "--locked",
        ],
    ),

    environment: {
        "TZ": "UTC",
        "LANG": "C.UTF-8",
    },

    sandbox: Hermetic,
    network: Denied,

    outputs: [
        Executable("target/release/forgeyard"),
    ],
)

193. Example forgeyard.lock Concept

(
    schema: 1,

    nodes: {
        "rust": (
            kind: RustToolchain,
            requested: "stable",
            resolved_version: "1.90.0",
            manifest_digest: "sha256:...",
        ),

        "project-source": (
            kind: Git,
            repository: "https://example.invalid/forgeyard.git",
            requested: "main",
            revision: "0123456789abcdef...",
            tree_digest: "blake3:...",
        ),
    },
)

The production schema should be frozen only after canonical serialization and compatibility rules are tested.


194. End-to-End Build and Release Flow

Developer changes source
      ↓
forgeyard lock --check
      ↓
source snapshot
      ↓
immutable lock graph
      ↓
derivation graph
      ↓
input trust/policy
      ↓
substituter lookup
      ↓
fetch missing immutable inputs
      ↓
runner selected
      ↓
read-only closure materialized
      ↓
deterministic environment synthesized
      ↓
network isolated
      ↓
builder executes
      ↓
output contract validated
      ↓
output normalized
      ↓
content digest calculated
      ↓
atomic store commit
      ↓
tests against immutable output
      ↓
independent reproduction
      ├── mismatch -> quarantine + diagnostics
      └── match
             ↓
deterministic package derivation
             ↓
runtime closure validation
             ↓
SBOM + provenance
             ↓
signing
             ↓
release manifest
             ↓
promotion by digest
             ↓
regional/P2P/object-store distribution
             ↓
deployment of exact artifact

195. The “Works on My Machine” Guarantee

Forgeyard should not claim that identical source text alone guarantees identical output.

Instead it should provide a much stronger statement:

A release was built from a cryptographically identified source and locked dependency closure, with explicitly identified toolchains and platform requirements, inside a controlled build environment with undeclared host state and network access blocked; the resulting output was content-addressed and independently reproduced before the exact same bytes were promoted for distribution.

That is a defensible CI/CD architecture.


196. Source-Inspired Design Notes

The subsystem deliberately adopts several documented Nix ideas as inspiration:

  • Nix treats packages in a purely functional style rather than as mutable global installations.
  • Nix derivations represent build dependency information.
  • Store objects are identified through cryptographic addressing schemes.
  • Existing realizations can be substituted rather than rebuilt.
  • Flake lock files pin external inputs for repeatable evaluation.

Forgeyard extends these ideas with:

  • explicit derivation-vs-realization separation;
  • content hashing of actual outputs;
  • independent reproducibility verification;
  • cross-platform platform contracts;
  • CI runner scheduling;
  • deterministic packaging;
  • build-once/promote-many distribution;
  • native Forgeyard provenance and release policy.

197. Final Architectural Position

The catastrophic mutable-state problem is not solved merely by:

Docker
a fixed compiler image
a lockfile
a cache

Each helps, but each solves only part of the problem.

The complete Forgeyard chain should be:

Declarative intent
      ↓
Immutable locked inputs
      ↓
Canonical derivation
      ↓
Hermetic realization
      ↓
Content-addressed output
      ↓
Independent reproduction
      ↓
Deterministic package
      ↓
Runtime closure validation
      ↓
SBOM + provenance
      ↓
Signature
      ↓
Promotion of identical bytes
      ↓
Digest-addressed distribution

The important question is no longer:

“Which machine built this successfully?”

It becomes:

“Which exact immutable derivation produced this exact content, from which locked closure and toolchains, under which enforced execution policy, and did an independent builder reproduce it?”

That is the architecture Forgeyard needs if reproducibility and elimination of “works on my machine” failures are foundational product requirements rather than optional CI conveniences.


References

The following primary Nix documentation informed the design principles in this document:

  1. Nix Reference Manual — introduction to the purely functional package-management model.
  2. Nix Reference Manual — derivations and build-time dependency representation.
  3. Nix Reference Manual — store/content-addressing behavior.
  4. Nix Reference Manual — flake locking and pinned input resolution.
  5. Nix Reference Manual glossary — derivations, store objects, references, and binary caches.

Forgeyard’s proposed design is its own architecture and is not intended to claim wire, language, or store-format compatibility with Nix unless an explicit interoperability adapter is implemented.