Skip to content

The Drift Log · Model independence and repeatable builds

Isolating Async Task Schedulers for Repeatable Builds

25 October 2026 · 3 min read · 650 words · established

Two identical crystal prisms casting exactly the same refraction

Eliminate build archive hash mismatches across heterogeneous CI runners by replacing ambient async task resolution with invariant-ordered queues.

Run the exact same build on an 8-core CI runner and a 16-core workstation. The commit hash is identical. The lockfile is identical. The compiler flags are identical. Yet the output archive produces a different SHA-256 hash.

When you unpack the build artifacts, the code is functionally the same, but internal module registration tables, asset manifests, or bundled chunk identifiers are shuffled.

The culprit is rarely the compiler itself. It is usually the host runtime's task scheduling. When build pipelines use ambient asynchronous operations to read files, resolve dependencies, or transform syntax trees, they yield control to an event loop whose resolution order depends on thread availability, kernel tick rates, and I/O latency.

How Concurrent Execution Breaks Build Parity

Asynchronous concurrency is standard practice to saturate CPU cores during compilation. A build script reads forty files using standard async primitives, passes them into a worker pool or a Promise.all construct, and appends the transformed outputs to an aggregate bundle as each operation completes.

// Non-deterministic: output order depends on I/O resolution speed
async function bundleModules(filePaths: string[]): Promise<string> {
  const registry: string[] = [];
  await Promise.all(
    filePaths.map(async (path) => {
      const compiled = await transformFile(path);
      registry.push(compiled); // Race condition on resolution order
    })
  );
  return registry.join("\n");
}

This pattern introduces non-determinism at the microtask boundary. Even without disk contention, microtask queues drain differently depending on host load. If worker thread 3 finishes parsing five milliseconds before worker thread 1, its output lands earlier in the array.

The resulting artifact behaves correctly in testing, but the binary differs on every host. Beyond breaking reproducible builds, this variance leaks into integration suites, producing flaky tests where assertions depend on implicit collection ordering or race-prone initialization paths.

Isolating Task Scheduling to an Explicit Queue

To achieve deterministic software, execution order cannot be left to ambient host timers or raw microtask resolution. Concurrency can still be used for throughput, but the collection and reduction of asynchronous tasks must be strictly ordered by invariant keys rather than completion time.

There are two practical patterns for enforcing this:

  1. Pre-allocated Indexed Collection: Dispatch concurrently, but write results strictly to fixed indices based on a pre-sorted input list.
  2. Explicit Deterministic Queue: Buffer completed tasks and drain them through a deterministic reducer.
// Deterministic: execution is concurrent, but emission order is fixed
async function bundleModulesDeterministic(filePaths: string[]): Promise<string> {
  // 1. Sort inputs to establish an absolute invariant order
  const sortedPaths = [...filePaths].sort();

  // 2. Map directly to preserve array index invariant across resolutions
  const compiledModules = await Promise.all(
    sortedPaths.map((path) => transformFile(path))
  );

  // 3. Emit in predictable order
  return compiledModules.join("\n");
}

When building multi-stage pipelines where tasks dynamically spawn sub-tasks, indexed mapping is not enough. You must implement an explicit task scheduler that holds ready-to-run jobs in a priority queue sorted by an explicit topological key (such as a normalized file path or dependency graph depth), stepping execution deterministically rather than relying on unmanaged native event loop ticks.

Determinism in the SHPBL Architecture

At SHPBL, repeatable builds are a baseline engineering constraint, not an afterthought. Our software corpus is model-independent: no AI model executes inside our runtime, and no component behaviour relies on dynamic generation at build time. We write logic that is computed rather than generated.

Every artifact we ship is evaluated by an automated harness that assigns an explicit status: CERTIFIED, PROVISIONAL, INCONCLUSIVE, or FAILED. Our release archives achieve byte-for-byte reproducibility, sealed against tampering using checksums published on our root of trust page. You can watch these verification pipelines execute in real time on our live harness.

To understand why removing runtime variance from your stack is an operational necessity, read why model independence is an engineering posture.

What to Audit on Monday

Eliminating scheduling jitter does not require rewriting your build tools from scratch. Audit your pipeline for these specific patterns:

  • Unsorted filesystem reads: Never pass the output of fs.readdir directly into an async worker. Sort path strings alphabetically before processing.
  • Global push inside async callbacks: Search your build and packaging scripts for array mutations (.push(), .set()) inside Promise.all or event emitter callbacks. Replace them with indexed assignments or sorted collections.
  • Ambient timer usage: Replace arbitrary setTimeout or setImmediate calls in test suites and orchestration scripts with explicit polling against state conditions or a controlled virtual queue.

Once task scheduling is bound to invariant data structures rather than runtime clock cycles, bitwise identical builds become the default across every runner host.

Keep reading

Next in the log

The Strategic Master Library · written and reviewed under the house's own epistemic rules: nothing claimed that we cannot show.