Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions THESIS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2062,6 +2062,28 @@ that's a gap.
- Adding a CI gate, a Node field, or a target language requires
editing one .dag file.

**Pure bootstrap (self-hosted stage0):**
- Editing compiler behavior requires editing `.dag` source only.
No matching hand edits to a Rust stage0 file. Cost-of-change
for any compiler concept — a new pass, a new substrate fact,
a new target-language detail — stays at 1.
- Stage0 is regeneratable from the `.dag` graph. Compiler
internals (tokenize, parse, lower, infer, emit, lenses, std
library) are emitted as Rust source and committed — not hand
authored. The hand-maintained surface is an irreducible shim
(CLI entry + runtime bridge), target ≤5 files. v2 achieves
this pattern at ~97% (2 hand-maintained of 62 stage0 files);
v3's trajectory is the Pure Bootstrap program (see
`docs/design-pure-bootstrap.md`).
- Fixed-point acceptance: v3 binary compiles `compiler.dag` →
produces bit-identical stage0 Rust + bit-identical emitted
artifacts. `compiler.dag`'s `hand_maintained_src` list
monotonically shrinks to the irreducible shim.
- Strictly stronger than "the compiler can compile itself":
the compiler's own source of truth is the `.dag` graph, and
the Rust tree is one realization of it — not a parallel
authority requiring manual sync.

**Modeling discipline:**
- Every declared type has at least one structural consumer.
- Every service boundary uses typed enums, not String/Bool proxies.
Expand Down
300 changes: 300 additions & 0 deletions docs/design-pure-bootstrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
# Pure Bootstrap (PB program)

**Thesis claim**: `THESIS.md` §"Pure bootstrap (self-hosted stage0)".

Editing compiler behavior must be a one-file `.dag` change. No paired
hand edits to a Rust stage0 file. This document is the trajectory from
today (78 hand-maintained `.rs` files in `src/v3/compiler`) to the
irreducible shim (target ≤5 files).

## Why this matters

Every hand-maintained Rust file in `src/v3/compiler/src/` is a parallel
authority to the `.dag` declarations it mirrors. Parallel authorities
drift. Per `feedback_parallel_representation_debt`: when a canonical
source exists, consume it rather than scaffold.

The cost-of-change principle in `CLAUDE.md` asks: "When the language
grows by one type, one expression, or one transport, how many files
need editing? The answer should be 1." Today the answer is 2+ (the
`.dag` authority plus the matching Rust stage0 file). Pure bootstrap
drives it to 1.

## Inspiration: v2's model

v2 achieves ~97% generated stage0:

| File class | Count | LOC |
|---|---|---|
| Generated (`std_*`, `v2_compiler_*`, `extdeps_languages_*`) | 60 | ~88,000 |
| Hand-maintained (`cli_run`, `v2_interpreter`) | 2 | ~2,900 |
| **Total stage0** | **62** | **~91,000** |

Key v2 properties (all replicated in v3's target end-state):

1. **Emitted Rust committed to tree** — no `include_str!` runtime
bootstrap of `.dag` files. The std library's primed Dag is baked
into generated Rust constructors at regen time.
2. **Generated headers** — `// Generated by v2 compiler -- do not edit.`
3. **Regen cycle** — `v2 compile → .ci-output/src/*.rs → cp → cargo build`
4. **Hand shim scope** — CLI entry (argv parsing) + tree-walking
interpreter for the runtime service. Both are
user-facing-boundary concerns, not compiler internals.

The "pure" part of pure bootstrap: **compiler internals have no hand
authority**. Only the system boundary does.

## Current v3 state

**Hand-maintained**: **78 `.rs` files** per the live SG-0 ratchet
(`EXPECTED_HAND_AUTHORED` in
`src/v3/compiler/tests/integration/sg0_census_test.rs`, partitioned
against `GENERATED_FILES` sourced from the producer-owned
`REGEN_OUTPUTS` manifest in `src/v3/compiler/build.rs`). Note:
`dsl/gunbc/compiler.dag`'s `hand_maintained_src` field is a
related-but-separate authority — it names files excluded from the
stage0 fixed-point diff (regeneration survival list), not the SG-0
ratchet source. PB metrics below track the SG-0 ratchet.

**Generated files on main** (partial dissolution already landed):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLOCKING: The “Current v3 state” inventory still names landed artifacts that are not on main (for example operators_generated.rs and most of the listed COMPILER_FILES), so the program is still not grounded in checked-in authority (Modeling Faithfulness Invariant).

- `lens_*_generated.rs` (5 files: cost, cost_symbolic, provenance,
structural_resolution, unused_parameters)
- `tokenize_generated.rs` (SG-1 authority via `src/v3/compiler/tokenize.dag` + `regen_tokenize`; `tokenize.rs` is now a 23-line shim but still on census)
- `operators_generated.rs` (SG-3; `operators.rs` retired)
- `dag_{branch,cluster,cost,scalar}_generated.rs`
- `infer_helpers_generated.rs`, `serialize_generated.rs`,
`diagnostics_generated.rs`, `types_generated.rs`

**Bootstrap inputs `Dag::new()` parses at runtime** (all four authorities):
1. **`std_fixtures`** — 7 `dsl/std/*.dag` files embedded via `include_str!` (`LOGIC_DAG`, `BIT_DAG`, `ALGEBRA_DAG`, `INTEGER_DAG`, `FLOAT_DAG`, `STRING_TYPE_DAG`, `TYPES_DAG`)
2. **`STAGED_FILES`** — `src/v3/std/*.dag` (build-script-enumerated)
3. **`V3_SPECS`** — `src/v3/spec/*.dag` (Rust/Go/Python language specs)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLOCKING: The current-state inventory still misstates live bootstrap authority by listing tokenize.dag inside COMPILER_FILES, but src/v3/compiler/build.rs explicitly filters tokenize.dag out of that array, so the PB-1 baseline is not yet fully grounded in producer-owned authority (Modeling Faithfulness Invariant / Single Authority).

4. **`COMPILER_FILES`** — `src/v3/compiler/*.dag` (operators.dag, pipeline.dag, regen.dag, runtime_mirrors.dag, tokenize.dag). Note: `dsl/gunbc/compiler.dag` is a separate authority, not part of `COMPILER_FILES`.

All four chain into a single `fixtures` array that bootstrap.rs tokenizes + parses + lowers on every `Dag::new()`. **PB-1 must replace the runtime tokenize/parse/lower path for all four input authorities**, not just `std_fixtures`.

**The 78-file gap decomposes** (approximate):
- ~30 compiler core files (parse, lower, infer, emit internals, tokenize shim, ...)
- ~10 lens files (SG-6 migrating to generated; some already done)
- ~10 diagnostic / utility files (serialize, diagnostics, types, ...)
- ~15 test infrastructure files
- ~10 bin files (regen_*, self_host_fixed_point, ...)
- ~3 bootstrap / lib files

## The PB program

Nine stages. Each stage is the **retirement step** that follows a
corresponding **authority-creation step** in the SG / Lane programs.
SG-N creates the `.dag` authority + its `_generated.rs` projection;
PB-N retires the hand-maintained Rust file that used to own the
concept. Two-step dissolution per concept: create authority, then
retire projection — not one.

### PB-0 — ratchet (unblocks everything)

- SG-0's live ratchet (`EXPECTED_HAND_AUTHORED` ∖ `GENERATED_FILES`
partition in `sg0_census_test.rs`) already enforces the
hand-authored count. Add **PB count** as a named column with a
trend arrow. Ratchet only down.
- **Target trajectory**: 78 → 50 → 20 → 5.
- **Dependencies**: none. Dispatchable now.
- **Not the same as `compiler.dag`'s `hand_maintained_src`** —
that's the regeneration-survival list (which files don't get
overwritten by the emitter); SG-0's `EXPECTED_HAND_AUTHORED`
is the ratchet (which files are still hand-authored). Both
should shrink in sync; when they diverge, the SG-0 ratchet is
authoritative for PB count.

### PB-1 — bootstrap loader emission (all four authorities)

- Replace `bootstrap.rs`'s runtime `include_str!` + tokenize + parse +
lower for **all four input authorities** (`std_fixtures` +
`STAGED_FILES` + `V3_SPECS` + `COMPILER_FILES`, per current-state
section above). Covering only `dsl/std/*.dag` is insufficient —
`Dag::new()` today chains all four into a single `fixtures` array.
- Output: `bootstrap_generated.rs` that calls `Dag::push_*` builders
to construct the primed Dag directly at `Dag::new()`, with no
runtime tokenize/parse/lower path.
- Matches v2's pattern (no runtime `.dag` parsing at bootstrap).
- **Dependencies**: Dag builder API (TM-1); generator that walks
the 4 authorities at build time and emits the constructor module.
- **Not done in one shot**: can stage per-authority (e.g., ship
`std_fixtures` first as PB-1a, then STAGED/SPECS/COMPILER as
PB-1b/c/d). Each sub-stage dissolves one include_str block + its
runtime parse path.

### PB-2 — tokenize retire

- **SG-1 authority already landed**: `src/v3/compiler/tokenize.dag` +
`regen_tokenize` + `tokenize_generated.rs` all exist on main.
- `tokenize.rs` is already a 23-line shim routing to generated.
- **PB-2 work**: delete `tokenize.rs` entirely, route call sites
directly through `tokenize_generated.rs` (or a `pub use` in
`lib.rs`); remove from `EXPECTED_HAND_AUTHORED` (SG-0 ratchet)
and from `compiler.dag`'s `hand_maintained_src` (regen-survival list).
- **Dependencies**: none (authority landed).
- **Counter delta**: -1 file. Dispatchable now.

### PB-3 — parse retire

- **Authority creation**: in progress via SG-2 (parse cutover).
SG-2 prep (#557) snapshotted incumbent output; full cutover
authoring `parse.dag` + `regen_parse` + `parse_generated.rs` is
the SG-2 followup lane.
- **PB-3 work** (after SG-2 authority lands): delete `parse.rs`,
route through generated.
- **Dependencies**: SG-2 full cutover landed.
- **Counter delta**: -1 file (~2K LOC — biggest single hand file).

### PB-4 — lower retire

- **Authority creation**: requires `lower.dag`. SG-3 series retired
specific support files (operators.rs); full `lower.dag` authority
is larger scope and not yet on the SG queue.
- **PB-4 work** (after authority lands): delete `lower.rs` + support.
- **Dependencies**: lower.dag authority creation lane (to be named).
- **Counter delta**: -1 primary file + supporting files.

### PB-5 — infer retire

- **Authority creation**: SG-4. SG-4-prep-a (result_port read
authority) landed. Full `infer.dag` + `regen_infer` +
`infer_generated.rs` triplet is the next SG-4 dispatch.
- **PB-5 work** (after SG-4 authority lands): delete `infer.rs` +
inference helpers that the generated module subsumes.
- **Dependencies**: SG-4 full dispatch.
- **Counter delta**: -1 primary file + helpers (~5-10 files).

### PB-6 — emit retire

- **Authority creation**: Lane 1e — single walker + per-target spec
tiles (`src/v3/spec/rust.dag`, `go.dag`, `python.dag`). Design doc
in `docs/single-emitter-design.md`.
- **PB-6 work** (after Lane 1e lands): delete `emit.rs`,
`emit/rust_target.rs`, `emit/python_target.rs`, `emit_go.rs`,
`emit_python.rs`, `emit_rust.rs` — everything consumed by the
spec-driven walker.
- **Dependencies**: Lane 1e complete.
- **Counter delta**: -6 to -8 files, -10K LOC. Biggest counter move.

### PB-7 — full fixed-point

- **DB-8 already runs** `self_host_fixed_point` in CI on a fixture
(`default_fixed_point_source`).
- **PB-7 work**: extend the gate's source input to `compiler.dag`
itself — v3 compiles `compiler.dag` → emits complete stage0 →
git-diff clean against checked-in stage0.
- **Dependencies**: PB-2 through PB-6 landed (generated Rust must
match checked-in stage0 bit-for-bit).

### PB-8 — graduation

- `EXPECTED_HAND_AUTHORED` (SG-0 ratchet) reaches ≤5 entries (CLI
entry + runtime bridge + build shim + possibly lib.rs + bootstrap
entry). `compiler.dag`'s `hand_maintained_src` converges to the
same set.
- Delete scaffolding: `include_str!` constants, runtime-parse paths,
census accommodations for not-yet-retired files.
- **Dependencies**: PB-7 green.

## Acceptance criteria (strict)

1. Every `.rs` file in `src/v3/compiler/src/` is **either**:
- Generated (ends in `_generated.rs` and has the generated header), **or**
- Listed in `EXPECTED_HAND_AUTHORED` (target: ≤5 entries; `compiler.dag`'s `hand_maintained_src` converges to the same set).
2. `cargo run -p v3-compiler --release --bin regen_v3` produces
bit-identical Rust (cycle converges in 1 iteration).
3. CI gate: census partition matches directory contents (no
unlisted hand-maintained files, no unmarked generated).
4. No middle state: no file is "generated but hand-edited for
emergency reasons." Edge cases fail-closed via Diagnostic.

## Irreducible shim (target state)

Candidates for the ≤5 hand-maintained files:
1. **CLI entry** — clap argv parsing, stderr routing (analogous to v2's `cli_run.rs`).
2. **Runtime shell bridge** — filesystem ops, process spawn for `dag run` interpreter service (analogous to v2's `v2_interpreter.rs`).
3. **Build shim** — `build.rs` for build-script codegen glue.
4. **lib.rs** — module declarations; ideally generated, but depends on cargo's constraints.
5. **Possibly**: a minimal entry to `Dag::new()` that the generated bootstrap calls.

If (4) and (5) can be generated, the shim is 3 files.

## Measurement

| Milestone | SG-0 PB count | Trigger |
|---|---|---|
| Today | 78 | — |
| Post-PB-2 | 77 | tokenize.rs deleted |
| Post-PB-3 | 76 | parse.rs deleted |
| Post-PB-4 | 75 | lower.rs deleted (+ support) |
| Post-PB-5 | 74 | infer.rs deleted |
| Post-PB-6 | 68 | emit files collapsed (~6 deletions) |
| Post-cleanup | ~50 | diagnostic/utility files migrated |
| Post-PB-7 | ~20 | test infra migrated |
| Post-PB-8 | ≤5 | graduation |

## Dependencies map

```
PB-0 (ratchet) ──────────────┐
↓
TM-1 (Dag builder) ──→ PB-1 (bootstrap loader)
↓
SG-1 (merged) ───────→ PB-2 (tokenize)
SG-2 full cutover ──→ PB-3 (parse)
SG-3 series ────────→ PB-4 (lower)
SG-4 complete ──────→ PB-5 (infer)
Lane 1e complete ──→ PB-6 (emit) ──────────┐
↓
PB-7 (full fixed-point)
↓
PB-8 (graduation)
```

## Open questions

1. **Binary blob vs. emitted Rust**: v2 uses emitted Rust; this doc
recommends the same for v3. A serialized `.blob` format was the
initial proposal — simpler stage0 but opaque in git. Emitted Rust
is human-readable and follows v2's proven pattern.
2. **Dag builder API scope**: TM-1 proposes `push_*` builders for
lens unit tests. PB-1 extends the same API for the bootstrap
loader. Design them together to avoid rework.
3. **`lib.rs` generation**: cargo expects `lib.rs` in a fixed path
with module declarations matching file layout. May need a
generator to materialize `lib.rs` from a declaration list.
4. **Graduation criteria for Lane 1e**: PB-6 depends on Lane 1e, but
Lane 1e's acceptance is currently "single walker + specs." Need
to verify that delivers a single emit file (not a slightly-reduced
three-file set).

## Non-goals

- **Not** a rewrite to a binary-blob format. v2's emitted-Rust
pattern works; follow it.
- **Not** removal of `cargo build` — stage0 remains a Cargo crate.
- **Not** eliminating bootstrap entirely — Dag::new() still runs,
but it calls generated constructors instead of parsing strings.

## Relationship to other tracks

- **SG program** (Self-Generation): SG retires specific Rust files
by authoring their `.dag` counterparts. PB is the **program that
names the endpoint**: SG-N deliverables feed PB-N stages.
- **TM program** (Test Migration): TM-1 (Dag builder API) is a PB-1
prerequisite. Co-design.
- **Lane 1e**: PB-6 is Lane 1e's downstream graduation. When Lane 1e
ships, PB-6 lands the file retirement.
- **Lane 3c** (self-hosting cycle): PB-7's full fixed-point IS Lane
3c's acceptance, extended from fixture scope to full-compiler scope.

## Graduation trigger

PB graduates when:
1. `EXPECTED_HAND_AUTHORED.len() ≤ 5` (SG-0 ratchet authority); `compiler.dag`'s `hand_maintained_src` converges to the same set
2. DB-8 self-host fixed-point runs on full `compiler.dag` (not fixture) and passes bit-identically
3. CI census gate blocks unmarked hand-authored files

At that point, the THESIS.md §"Pure bootstrap" claim is structurally
satisfied. The residual shim is the irreducible system boundary.
Loading