Quartz v5.25

Quartz Guard — Source-Only Build Verification

Problem

Quartz is self-hosted: the committed binary at every commit was built from that commit’s source by a previous binary, which may have had compiled-in definitions that no longer exist in the current source. When references to those definitions survive in the source but the definitions are gone, the source becomes fossilized — it only builds if you use a pre-existing binary that still remembers the ghost. A pure source-only build (start from a known-good gen0, compile every forward commit in order) will fail at the fossil commit and every commit downstream of it.

This is exactly what the 2026-03 → 2026-04 Linux bootstrap walk exposed. Multiple fossils were discovered and have since been fully resolved (verified Apr 12, 2026):

FossilIntroducedStatus (Apr 12, 2026)
ast_func_is_cconv_c015b0305 (Apr 3)✅ Stub at ast.qz:1489 (returns 0, intentional)
TYPE_MAP constant97d088ce (Apr 6)✅ Defined at type_constants.qz:90
NODE_IS_CHECK constant~Apr 6✅ Defined at node_constants.qz:257
tc_type_is_hashable / tc_struct_is_hashable / tc_hashable_rejection_reason / tc_struct_hashable_rejectionApr 6✅ Full impls in typecheck_helpers.qz:527-596
mir_register_poll_callee015b0305✅ Full impl at mir.qz:62
mir_func_set_cconv_c / mir_func_is_cconv_c015b0305✅ Full impl at mir.qz:1156-1168
mir_block_set_switch / mir_block_get_switch_values / mir_block_get_switch_targetsPre-54eb4965✅ Full impl at mir.qz:1479-1511
cg_emit_struct_hash_eq_functions / cg_emit_custom_map_functionsPre-54eb4965✅ Full impls in codegen_runtime.qz:6351/6499

Source-only build verified: gen1 (1,679,869 lines IR) == gen2 byte-identical. The standing patch set has been retired — no patches are required for a clean source-only build from HEAD.

Each fossil originally cost hours of bootstrap walking to identify and patch around. The resolution ensures this class of problem cannot recur undetected.

The Guard — invariant to maintain

For every commit on trunk, compiling self-hosted/quartz.qz from source using the previous commit’s binary (plus any required source-level patches that are intentional forward steps) must succeed.

The “plus patches” escape hatch is for legitimate cases where a commit introduces new syntax or builtins — the patch set at that commit would be the minimal diff a user would apply to cross-compile the feature. The important property: no dangling references to definitions that don’t exist anywhere in the source tree.

Implementation — three phases

Phase 1 — pre-commit local check (fast, per-commit)

Before committing, run:

./self-hosted/bin/quake guard:local

This task (to be added to Quakefile.qz) runs:

  1. git stash the working tree
  2. git checkout HEAD~1 -- self-hosted/bin/quartz self-hosted/bin/quake (use the previous commit’s binary)
  3. git stash pop
  4. Run ./self-hosted/bin/quake build (the previous binary compiling the current source)
  5. If it fails, report which symbols couldn’t be resolved and abort the commit

Budget: ~30 seconds (debug build). Acceptable as a pre-commit hook for the ~20 commits per day on active feature work. For commits that intentionally introduce a new feature with a chicken-and-egg gap, the developer adds a guard-patch: line to the commit trailer pointing at a .quartz-guard/NNN-patch.sh script that the guard runs before the build.

Phase 2 — CI forward-rebuild (slow, per-PR)

On every PR, run guard:forward:

  1. Check out the merge base with trunk
  2. Use that commit’s committed binary as gen0
  3. For each commit in the PR, in order:
    • Apply any .quartz-guard/NNN-patch.sh scripts present in the diff
    • Build from source using the previous gen binary
    • Run quake fixpoint on the last commit
  4. Report the first commit (if any) where source-only build fails

Budget: 5-15 minutes depending on PR size. GitHub Actions, triggered on push to any PR branch.

Phase 3 — CI historical sweep (weekly, full history)

Weekly job that walks the last 30 days of commits on trunk source-only, starting from a known-good binary 30 days back. Catches fossils that made it past both Phase 1 and Phase 2 (e.g., cross-commit interactions where two separately-valid commits combine into a fossil). On failure, the CI opens an issue on GitHub with the commit range and the missing symbol.

Budget: ~2 hours per run. Once a week on Sunday night.

The “Guard” binary — bootstrap-quality compiler

A second committed binary at self-hosted/bin/quartz-guard that is specifically built and maintained to be fixpoint-stable from the minimal fossil-free source tree. Its job is only to serve as the gen0 for source-only rebuilds. It doesn’t need to be fast, doesn’t need mimalloc, doesn’t need the latest optimizations — it just needs to faithfully compile the source.

Whenever a fossil is introduced and then resolved, the guard binary is rebuilt from the post-resolution commit so Phase 2 has a clean starting point moving forward.

Patch-set protocol for intentional chicken-and-egg commits

When a commit introduces a feature where the new source can’t build with the previous binary (e.g., 97d088ce introduced map_new registration AND the intrinsic registry initializer using map_new() at the same time), the commit author provides a .quartz-guard/<commit-hash>-patch.sh script that:

  1. Mutates the new source just enough to make it buildable with the previous binary
  2. Documents why (what compiled-in state the patch is substituting for)
  3. The Phase 2 CI applies the patch, builds, and the resulting binary is what’s used for the NEXT commit in the sequence

The patch file is committed alongside the code change. Once a second commit lands that moves past the chicken-and-egg, the patch file can be deleted.

Known patch set — RETIRED (Apr 12, 2026)

All permanent fossils have been resolved in source. The standing patch set is no longer needed. For historical reference, the patches that were required before resolution:

  • standing/01-strip-cconv-c.shast_func_is_cconv_c now has a stub definition
  • standing/02-type-map-const.shTYPE_MAP now defined in type_constants.qz
  • standing/03-node-is-check-const.shNODE_IS_CHECK now defined in node_constants.qz
  • standing/04-hashable-stub.shtc_type_is_hashable and friends now have full implementations
  • standing/05-tc-all-builtin-names.sh — no longer referenced as a fossil

The source tree is now source-only-buildable by any prior binary without patches.

Relationship to the Binary Backup Protocol

The existing Binary Backup Protocol (CLAUDE.md) protects against broken builds destroying the committed binary. The Quartz Guard protects against fossils making the source tree unbootstrappable even when the committed binary is fine. They’re orthogonal:

  • Binary Backup Protocol: “NEVER overwrite quartz-golden until fixpoint is verified”
  • Quartz Guard: “NEVER let source reference a symbol that doesn’t exist in source”

Both must hold simultaneously for the language to remain in a state where any developer can cross-compile from scratch without a pre-existing binary.

Golden binary backups from the Linux bootstrap walk

self-hosted/bin/backups/ contains the checkpoint binaries from the Linux bootstrap walk that rediscovered these fossils. Each is a Linux x86_64 ELF, fixpoint-verified at its commit:

quartz-linux-x64-<commit>-golden   # the compiler
quake-linux-x64-<commit>-golden    # the build tool

Walk order (forward in time):

bce5e646 → bbded285 → 27b133a1 → ac0ef57c → d12ea4b6 → 780a0079
          → 015b0305 → 32fa81b3 → 97d088ce → aef2610b → 0c73a2fc
          → 54eb4965 → afad28c0 → 7ba5fa12 (HEAD)

Any of these can serve as the gen0 for a future Linux bootstrap without mimalloc and without the cache-pattern codegen bug described in HANDOFF_LINUX_BOOTSTRAP.md.

Future work

  • Fix permanent fossils in source — ✅ DONE (Apr 12, 2026). All 11 fossils resolved.
  • Add quake guard:source to Quakefile.qz — ✅ DONE. Automated clean-room source-only build + fixpoint verification + 3 smoke tests (brainfuck, expr_eval, style_demo).
  • Add quake guard:forward (per-PR CI) and quake guard:history (weekly sweep)
  • Enable Phase 1 as a pre-commit hook via .claude/settings.json hooks
  • Enable Phase 2 in GitHub Actions via .github/workflows/quartz-guard.yml
  • Fix the as_int(string) / as_string(int) round-trip codegen defect that caused the final Linux bootstrap blocker — this is a real runtime bug, not a source fossil