# Phase 0 — "Make the crate verifiable" — status Phase 0 of the plan in `CAD_ASSESSMENT_AND_PLAN.md`. Its purpose is to make changes to the CAD module *provable* rather than plausible. Before this work, the crate could not be resolved, built, linted, or tested at all. **Result: `nigig-build` now compiles and its test suite runs.** - CAD module: 0 compile errors (was 1). - Whole crate: 0 compile errors (was 44). - Test suite: **634 passed, 17 failed, 7 ignored** — the first baseline this crate has ever had. See `TEST_BASELINE.md`. The 44 sibling-module errors documented below as "deferred" were subsequently fixed; the section is kept as a record of what was wrong and why. CI now gates on the full crate. --- ## Completed ### 0.1 — All git dependencies pinned to a rev 33 manifests contained `branch = "..."` (or nothing at all) on git dependencies. Every build therefore re-resolved to whatever the branch head was — unreproducible by construction, and a direct code-execution path into the build if the branch is force-pushed. | Repository | Was | Now (`rev`) | |---|---|---| | `gitdab.com/andodeki/makepad` | `branch = "portallist_flow_adaptive_view"` | `16a4d04cbb21c82df323f4fe02133b9b260ed36f` | | `github.com/project-robius/robius` | *(unpinned — no branch or rev)* | `b766e62b0600f5d2ee21cc6995648346fc277bd8` | Both revs are the branch/HEAD commits as resolved on 2026-07-27, so this pins current behaviour rather than changing it. ### 0.2 — `Cargo.lock` committed `Cargo.lock` now exists at the workspace root: **540 packages, 488 locked**. `cargo metadata --locked` is a no-op against it. Producing it required fixing three genuine resolution failures (below) — the workspace had never resolved. ### 0.3 — CI workflow `.forgejo/workflows/nigig-build.yml`. Two jobs that are green today: - **`supply-chain`** — every live git dependency is pinned to a rev (comment lines excluded; verified with a negative test); `Cargo.lock` is committed and unchanged by resolution; no whitespace errors. - **`cad-module`** — installs the native GL/Wayland stack, checks formatting. - **`full-crate-check`** — `cargo check` on the whole lib, plus `cargo test` with a **regression gate**: the build fails if failures rise above the recorded baseline of 17 or passes fall below 634. This was commented out when first written (the crate had 44 compile errors); it is now live and required. Verified locally in both directions — it passes on the current tree and correctly rejects a simulated 18th failure. ### 0.5 — Stale documentation removed Deleted, having been shown to contradict the code: | File | Why | |---|---| | `cad/PROGRESS.md` | Claimed parallel mesh generation and the Send/Sync audit were "⏸ Deferred". Both shipped. Credited the command pattern to the `Legacy*` system, which is dead. | | `cad/README.md` | A patch-distribution note ("drop these files into…"). Told the reader to add `rayon` to `Cargo.toml` — already present. Claimed `build_glb()` is parallel by default — it is sequential below 32 nodes. | | `cad/SPLIT_PLAN.md` | A line-number map of a version of `mod.rs` that no longer exists. | | `cad/CHANGES.md` | A changelog for a superseded patch drop. | | `workspace/Next` | A raw AI chat transcript about *spreadsheet* features, committed into the tree. | Replaced by one file: **`cad/ARCHITECTURE.md`**, which describes the system as it is — data flow, the 24 files, the load-bearing invariants (including the ones that are currently *violated*, flagged as such), and the two independent meshing pipelines. ### 0.6 — Benchmarks no longer assert on wall-clock time All six `bench_*` functions in `profile_benchmarks.rs` are now `#[ignore]`-d, so `cargo test` no longer runs timing-sensitive code. Removed `assert!(warm < cold)` from `bench_scene_cache_hit_vs_rebuild`: it compared two single un-warmed samples and would flake under CI load. --- ## Resolution failures fixed along the way These blocked `Cargo.lock` entirely. None were introduced by this work. **1. `nigig-rider` requested a `makepad-widgets` feature that does not exist.** ``` package `nigig-rider` depends on `makepad-widgets` with feature `map_style` but `makepad-widgets` does not have that feature ``` Available: `voice`, `maps`, `gltf`, `csg`, `test`, `pdf`, `cef`, `serde`. Removed `map_style`, kept `maps`. **2. Two `rusqlite` versions both linking `sqlite3`.** ``` libsqlite3-sys v0.32 (robius-sms) vs v0.28 (matrix_client → rusqlite 0.31) note: only one package in the dependency graph may specify the same links value ``` This one is `nigig-build`'s own problem: it depends on `robius-sms` (rusqlite 0.32) *and* `nigig-core` → `matrix_client` (rusqlite 0.31). `nigig-pay-storage` and `robius-sms` already standardise on 0.32, so `matrix_client` was the lone straggler — bumped it to 0.32. **3. `spreadsheet-ui` did not compile** (4 × `E0308`). `grid.rs` had four `cells.get(&(r, c))` calls missed during a `(row, col)` → `CellId::new(row, col)` migration; the rest of the file was already migrated. Fixed to match. --- ## Fixed in the CAD module **`section_shape.rs` — `E0603`, private import re-export.** ```rust use super::math::DVec2; // math.rs imports DVec2 privately ``` Changed to `use makepad_widgets::DVec2;`. Also dropped a now-unused `std::f64::consts::PI` import. This was the **only** compile error in the entire CAD module. --- ## Sibling modules — 44 errors, now FIXED **Status: resolved.** Recorded here because the diagnosis is the useful part. None of these were CAD code; they blocked `cargo test` because the CAD workspace links against them (`cad/mod.rs:2055` calls `cost_estimator::script_mod(vm)`). | Module | Errors | Nature | |---|--:|---| | `doc/crdt_widget.rs` | 8 | `CrdtDocEditor` doesn't implement `WidgetNode`; missing `script_new`/`script_component`; 2 × "Unexpected field form" (a derive-macro rejection). Looks like a half-finished port to a newer Makepad widget API. | | `cost_estimator/services.rs` | 8 | 6 × `E0308`; `Room` type not in scope. | | `cost_estimator/data_raw.rs` | 8 | 8 × `E0308`. | | `doc/widgets/doc_widget.rs` | 6 | Missing `CrdtDocument::to_json`/`from_json`; a borrow conflict; `return;` in a non-`()` function. | | `cost_estimator/screen_actions.rs` | 5 | 4 × `E0308`; `&SizeCategory` used as a string pattern. | | `doc/projection_renderer.rs` | 4 | 4 × `E0382` use-after-move on font handles. | | `cost_estimator/room_controller.rs` | 2 | `String: Borrow` unsatisfied. | | `cost_estimator/ce_preview.rs` | 1 | `E0308`. | | `cost_estimator/screen_events.rs` | 1 | `set_header_metrics` is private. | Partially addressed: `screen_actions.rs` had 10, now 5 — `read_modal_f64` and `read_modal_u32` took `LiveId` but every call site passes `ids!(...)`, which is `&[LiveId; 1]`. Signatures corrected to `&[LiveId]`, matching the `text_input(cx, ids!(…))` convention used throughout. ### How they were fixed Three root causes, not 44 independent bugs: 1. **An unfinished `String` -> newtype migration in `cost_estimator/`.** `Room.room_type`/`size_category` had become `RoomTypeName`/ `SizeCategory`, but the JSON parsers, the dimension-lookup map, the UI DTO boundary and several tests still used `String`. Fixed by converting at the boundary (`.into()` inbound, `.as_str().to_string()` outbound) and keying the lookup maps on the newtypes. 2. **A derive-macro limitation in `doc/crdt_widget.rs`.** The `Script`/`Widget` derives reject a fully-qualified path type in a `#[rust]` field, emitting only "Unexpected field form" with no location. Found by bisection. `use doc_engine::controller::DocumentController;` plus a bare type name fixes it; a comment now records this so it is not rediscovered. 3. **Borrow-scope and copy-paste defects in `doc/`.** Four `&mut DrawText` moves inside a loop (needed reborrows); two `&self` calls made while a `&mut` borrow was live (hoisted); and one block in `delete_forward_command` that had been copied from the insert-text path and still referenced its `input` variable, with `return;` in a `-> bool` function. Forward-delete now replaces the selection with `String::new()`. Also implemented `CrdtDocument::to_json`/`from_json` (serde derives on the CRDT wire types) — these were called but had never been written. ### Test-build fixes The lib compiled before the tests did; `cargo test` surfaced a further 77 errors in test code, including a stray `doc/tests.rs` whose first 66 lines were a stale duplicate of `layout/layout_engine.rs` (its `super::` imports could not resolve from that path). Removed the duplicate, kept the 11 real tests. Two `cost_estimator` tests asserted `assert_ne!` between distinct newtypes — a comparison the type system now rejects at compile time, which is a stronger guarantee than the runtime assertion; rewritten to compare `as_str()` and renamed accordingly. --- ## Build environment The toolchain is not part of the workspace snapshot; re-create with: ```bash curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.97.1 rustup component add clippy rustfmt sudo apt-get install -y pkg-config libwayland-dev libxcursor-dev \ libxrandr-dev libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \ libpolkit-gobject-1-dev libpolkit-agent-1-dev libglib2.0-dev \ libssl-dev libsqlite3-dev libudev-dev ``` Toolchain 1.97.1 matches `rust-toolchain.toml`. --- ## Not done **0.4 — extend `cargo-deny` to `nigig-build`. DONE** (later; see `deny-nigig-build.toml`). The backlog I predicted was real: 5 advisories, 6 licence rejections, 126 git-source errors and 20 unlicensed first-party crates. The triage: - **1 actual vulnerability.** RUSTSEC-2026-0187, unbounded recursion in `lopdf` — a ~21 KB crafted PDF aborts the process with `SIGABRT`, and because it is a stack overflow rather than a panic, `catch_unwind` cannot contain it. **Not exploitable here**, verified rather than assumed: the advisory covers `Document::load*`, and this crate never parses a PDF. `arch_pdf.rs` only writes them, there is no `lopdf` import anywhere in the workspace, and no PDF read path exists. No fix is available in range either — `printpdf 0.7` pins `lopdf 0.31` and `cargo update -p lopdf` moves nothing. Ignored with the reasoning and, importantly, the condition that invalidates it: **the moment anything here reads a PDF, this becomes a live DoS.** - 4 unmaintained/unsound advisories, all transitive through the GUI and platform stack, none with a safe upgrade. Listed individually so a new one still fails. - 6 licence rejections, all permissive licences simply missing from the allow list. `MPL-2.0` (`option-ext`) is weak copyleft and is granted to that one crate rather than added globally. - The 126 git-source errors and 20 unlicensed crates are artifacts, not findings — rev-pinned git dependencies and first-party manifests with no `license` field. Handled without `allow`-ing the whole class, so a genuinely new unlicensed dependency still fails. Both directions verified: removing the `lopdf` ignore fails the gate, and removing `MIT` from the allow list produces 79 rejections. --- ## Next Phase 0's gate is met for the CAD module: it compiles, resolution is pinned and reproducible, CI enforces both, and the documentation no longer lies. Phase 1 (correctness) can begin. Recommended first item remains **1.1, the rotation-unit inconsistency** — it silently corrupts every export of a rotated part, and `ARCHITECTURE.md` now carries a warning against writing new code that reads `rotation_euler_xyz` until it is resolved.