From 3b4146e53050d3f07b29799d0aa80d28eddcf501 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 00:49:59 -0400 Subject: [PATCH] fix(dolt): remove non-reproducible dolt-data FOD; seed via runtime clone (prx-zj8) dolt clone+gc isn't byte-reproducible across builders, so the FOD's fixed hash mismatched on rebuild. Seed the volume by a builder-independent runtime clone of the pinned commit (deterministic per-commit) instead. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/dolt-data-runtime-seed.md | 10 ++++ flake.nix | 3 - nix/oci/dolt-data.nix | 80 --------------------------- packages/prx/src/room/dolt-service.ts | 22 +++++--- packages/prx/src/room/per-repo-pod.ts | 2 +- packages/prx/src/room/spec.ts | 4 +- 6 files changed, 27 insertions(+), 94 deletions(-) create mode 100644 .changeset/dolt-data-runtime-seed.md delete mode 100644 nix/oci/dolt-data.nix diff --git a/.changeset/dolt-data-runtime-seed.md b/.changeset/dolt-data-runtime-seed.md new file mode 100644 index 00000000..1256a919 --- /dev/null +++ b/.changeset/dolt-data-runtime-seed.md @@ -0,0 +1,10 @@ +--- +"@bounded-systems/prx": patch +--- + +Remove the `dolt-data` nix FOD — `dolt clone`+`gc` are NOT byte-reproducible +across builders (proven: same pinned commit → different NAR hashes on Lima vs the +container builder), so a content-addressed fixed-output derivation is the wrong +model. The beads data volume is seeded by a builder-INDEPENDENT runtime clone of +the pinned commit instead (deterministic per-commit; the path `prx dolt provision` +should take when wired). Updates the dolt-service / pod-spec docs accordingly. diff --git a/flake.nix b/flake.nix index 48499a59..2f781cf0 100644 --- a/flake.nix +++ b/flake.nix @@ -68,9 +68,6 @@ keeperd-box = import ./nix/oci/keeperd-box.nix self { inherit pkgs system; }; forge-d-box = import ./nix/oci/forge-d-box.nix self { inherit pkgs system; }; dolt-box = import ./nix/oci/dolt-box.nix self { inherit pkgs system; }; - # The deterministic beads dolt-data artifact (prx-asr): the - # network-fetch stage; the pod volume is populated from it (no network). - dolt-data = import ./nix/oci/dolt-data.nix self { inherit pkgs system; }; # The nix remote BUILDER as a pinned container (prx-zj8 capstone) — # replaces the Lima builder VM (sshd + single-user nix on a /nix volume). nix-builder-box = import ./nix/oci/nix-builder-box.nix self { inherit pkgs system; }; diff --git a/nix/oci/dolt-data.nix b/nix/oci/dolt-data.nix deleted file mode 100644 index c00a6d33..00000000 --- a/nix/oci/dolt-data.nix +++ /dev/null @@ -1,80 +0,0 @@ -# The beads dolt-data build artifact (prx-asr data layer). -# -# The owner steer: the pod's beads data should be a deterministic, build-like -# artifact that SEPARATES network-fetch from copy, so dolt-box is standalone. -# This derivation is the **network-fetch stage**: a fixed-output derivation -# (FOD) that clones the DoltHub remote, pins to a specific commit, and emits a -# content-addressed dolt data dir. The **copy stage** (populate the pod's -# `prx-dolt-data` named volume from this artifact) happens later at pod-provision -# time with NO network — see packages/prx/src/room/dolt-service.ts. -# -# Determinism: the DoltHub remote HEAD moves (it's the live canonical mirror), so -# we pin a COMMIT (`pinnedCommit`) as the anchor — `dolt clone` fetches current -# HEAD, then we `dolt reset --hard` the default branch to the pinned commit and -# `dolt gc` so the store carries only chunks reachable from it (dolt forbids a -# detached HEAD, so reset rather than checkout). Reproducible PER-COMMIT; the -# recursive-NAR `outputHash` pins the exact bytes (bump both together to advance -# the data). FOD ⇒ network is allowed under the sandbox. -# -# Build (offloads to the prx-62h linux builder from a macOS host): -# nix build .#packages.aarch64-linux.dolt-data -self: -{ pkgs, system ? pkgs.stdenv.hostPlatform.system }: -let - remote = "doltremoteapi.dolthub.com/bounded-systems/prx"; - database = "io_github_bounded_systems_prx"; - # Determinism anchor — the DoltHub commit this artifact pins (advance with the - # outputHash together). Captured 2026-06-28 from the canonical clone HEAD. - pinnedCommit = "bb6ekib413d7lc3r543vj60fh5m53nes"; -in -pkgs.stdenvNoCC.mkDerivation { - name = "dolt-data-${database}"; - nativeBuildInputs = [ pkgs.dolt pkgs.cacert ]; - - # FOD: network allowed; output content-addressed by the NAR hash of the tree. - outputHashMode = "recursive"; - outputHashAlgo = "sha256"; - outputHash = "sha256-RA3DAIKHOZXSwFofd5x0Wnhs5tsGOzDEw4wfEchXkAg="; - - SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; - - # No src; the whole build is the clone+pin. - dontUnpack = true; - - buildPhase = '' - runHook preBuild - export HOME="$PWD/.home" - mkdir -p "$HOME" - echo "dolt-data: cloning ${remote} (network stage)..." - # Clone into a dir named for the database — dolt sql-server --data-dir serves - # each immediate subdir-with-.dolt as a database by that dir name, and beadsd - # connects to `${database}`. - dolt clone ${remote} ${database} - cd ${database} - echo "dolt-data: pinning the default branch to commit ${pinnedCommit}..." - # dolt forbids a detached HEAD; reset the (checked-out default) branch to the - # pinned commit so the working set + branch point at the anchor. - dolt reset --hard ${pinnedCommit} - echo "dolt-data: gc (drop chunks unreachable from the pinned commit)..." - dolt gc - cd .. - runHook postBuild - ''; - - installPhase = '' - runHook preInstall - # $out is a DATA-DIR: it holds the database as a subdir, so populating the - # pod's /var/lib/dolt volume from $out yields /var/lib/dolt/${database}/.dolt. - mkdir -p "$out/${database}" - # Drop only the runtime server lock (recreated per run). repo_state.json is - # REQUIRED by dolt (records the branch/working-set) — keep it; after - # reset --hard + gc it is deterministic for the pinned commit. - rm -f ${database}/.dolt/sql-server.lock 2>/dev/null || true - cp -r ${database}/. "$out/${database}/" - runHook postInstall - ''; - - meta = { - description = "Deterministic beads dolt-data artifact (DoltHub clone pinned to ${pinnedCommit})"; - }; -} diff --git a/packages/prx/src/room/dolt-service.ts b/packages/prx/src/room/dolt-service.ts index 4cda668a..b1e1fa3e 100644 --- a/packages/prx/src/room/dolt-service.ts +++ b/packages/prx/src/room/dolt-service.ts @@ -37,14 +37,20 @@ export const DOLT_BOX_ENV: Readonly> = { }; /** - * The deterministic data SEED is the `dolt-data` nix FOD - * (nix/oci/dolt-data.nix) — the network-fetch stage. The COPY stage populates - * the {@link DOLT_DATA_VOLUME} from that artifact with NO network, via: + * SEEDING the data volume — a RUNTIME clone, not a nix build artifact. * - * tar -C "$(nix path)" -cf - . | podman volume import prx-dolt-data - - * podman run --rm -v prx-dolt-data:/d alpine chmod -R a+rwX /d # nix store is read-only + * A content-addressed nix FOD was tried (the old nix/oci/dolt-data.nix) but + * `dolt clone` + `dolt gc` are NOT byte-reproducible across builders (or runs), + * so the FOD's fixed output hash mismatched on any rebuild — proven: the same + * pinned commit yielded different NAR hashes on the Lima vs the container + * builder. So the seed is builder-INDEPENDENT and deterministic only PER-COMMIT: + * clone the DoltHub remote at the pinned commit straight into the volume: * - * (podman-machine can't see the host /nix/store, so a tar stream — not a bind — - * is the transport; the chmod makes the read-only store bytes writable for the - * dolt server.) Wired into pod provisioning in the pod-model phase. + * podman run --rm -v prx-dolt-data:/var/lib/dolt:U -e HOME=/tmp \ + * dolt clone /var/lib/dolt/io_github_bounded_systems_prx + * # then `dolt reset --hard ` for the pin (optional) + * + * One-time at provision; the volume then persists. This is what `prx dolt + * provision` (the stubbed GH-1685 verb) should do when wired — no nix FOD, no + * cross-builder hash to break. */ diff --git a/packages/prx/src/room/per-repo-pod.ts b/packages/prx/src/room/per-repo-pod.ts index 4b41e452..e2bc43a8 100644 --- a/packages/prx/src/room/per-repo-pod.ts +++ b/packages/prx/src/room/per-repo-pod.ts @@ -29,7 +29,7 @@ import type { PodService } from "./spec.ts"; * dolt-box — the standalone dolt SQL server (a backing service, NOT a room): it * owns the beads database on the {@link DOLT_DATA_VOLUME} named volume and serves * the MySQL wire on the pod netns, which beadsd-box connects to. Seeded out-of- - * band from the dolt-data FOD (see dolt-service.ts). No doors. + * band by a runtime clone of the pinned commit (see dolt-service.ts). No doors. */ const doltService: PodService = { name: "dolt", diff --git a/packages/prx/src/room/spec.ts b/packages/prx/src/room/spec.ts index 978ab1bd..ef84aabd 100644 --- a/packages/prx/src/room/spec.ts +++ b/packages/prx/src/room/spec.ts @@ -189,8 +189,8 @@ export const PodServiceSchema = z.object({ * A named volume holding the service's persistent state, mounted at * `mountPath`. Rendered as a `persistentVolumeClaim` (podman kube play maps * the claim name to a podman named volume, auto-creating it if absent and - * preserving it across `kube down`). Seeded out-of-band (e.g. the dolt-data - * FOD → `podman volume import`). + * preserving it across `kube down`). Seeded out-of-band (e.g. a runtime + * `dolt clone` of the pinned commit into the volume — see dolt-service.ts). */ dataVolume: z.object({ name: z.string().min(1), mountPath: z.string().min(1) }).optional(), /** Environment for the service container. */