Repository navigation
docs: add Pure Bootstrap thesis claim + PB program design #581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8e332d1
4367938
e8217e0
4f6eaaf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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): | ||
| - `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) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BLOCKING: The current-state inventory still misstates live bootstrap authority by listing |
||
| 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. | ||
There was a problem hiding this comment.
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.rsand most of the listedCOMPILER_FILES), so the program is still not grounded in checked-in authority (Modeling Faithfulness Invariant).