Blocked since Phase 0 because cargo-deny resolves from the lockfile, and unblocked once Cargo.lock was committed in 0.2. I predicted this would surface a large backlog. It did: 5 advisories, 6 licence rejections, 126 git-source errors and 20 unlicensed crates. The triage is the work here, not the config. ONE ACTUAL VULNERABILITY. RUSTSEC-2026-0187: unbounded recursion parsing nested PDF objects in lopdf 0.31, reached via printpdf 0.7. A ~21 KB crafted file aborts the process with SIGABRT, and because it is a stack overflow rather than a panic, catch_unwind cannot contain it. Not exploitable here, and I checked rather than assumed. The advisory is specifically about Document::load*, the parsing entry points. This crate never parses a PDF -- arch_pdf.rs only writes them through printpdf's drawing API, there is no lopdf import anywhere in the workspace, and there is no PDF read path of any kind. There is also no fix in range: printpdf 0.7 pins lopdf 0.31 and `cargo update -p lopdf` moves nothing. Clearing it means printpdf 0.8+, a breaking change across arch_pdf.rs. So it is ignored with the reasoning written down AND with the condition that invalidates it: the moment anything in this crate reads a PDF -- an import feature, a thumbnailer, a preview pane -- this becomes a live DoS and the ignore must go. An ignore without its expiry condition is how real vulnerabilities get inherited. The other four advisories are unmaintained/unsound transitive crates (proc-macro-error, ttf-parser, atomic-polyfill, glib VariantStrIter), none with a safe upgrade, all arriving through the GUI/platform stack. Listed individually rather than disabling the unmaintained class, so a new one still fails. Six licence rejections were permissive licences simply absent from the allow list. MPL-2.0 (option-ext) is weak copyleft, so it is granted to that one crate rather than added globally -- MPL reciprocity is per-file and only bites if the crate is vendored and edited. The 126 git-source errors and 20 unlicensed crates are artifacts, not findings. Every git dependency is pinned to a full rev, which is stronger than a version range and already enforced by its own CI step; the unlicensed crates are first-party and vendored forks with no license field. Both handled WITHOUT allow-ing the class: sources are governed by the rev-pinning step, and the 20 crates are clarified by name, so a genuinely new unlicensed dependency still fails. Blanket-disabling would have hidden exactly the case worth catching. Separate file from deny.toml on purpose. Merging them would mean loosening the payment rules to fit a Makepad + Robius graph, and the payment policy is the one worth keeping tight. Verified both pay crates still pass unchanged. Negative-tested in both directions: removing the lopdf ignore fails the gate, and removing MIT from the allow list produces 79 rejections. A gate that cannot fail is worse than no gate. 622 lib + 154 integration, 0 failed.
12 KiB
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.lockis committed and unchanged by resolution; no whitespace errors. -
cad-module— installs the native GL/Wayland stack, checks formatting. -
full-crate-check—cargo checkon the whole lib, pluscargo testwith 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.
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<RoomTypeName> 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:
-
An unfinished
String-> newtype migration incost_estimator/.Room.room_type/size_categoryhad becomeRoomTypeName/SizeCategory, but the JSON parsers, the dimension-lookup map, the UI DTO boundary and several tests still usedString. Fixed by converting at the boundary (.into()inbound,.as_str().to_string()outbound) and keying the lookup maps on the newtypes. -
A derive-macro limitation in
doc/crdt_widget.rs. TheScript/Widgetderives 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. -
Borrow-scope and copy-paste defects in
doc/. Four&mut DrawTextmoves inside a loop (needed reborrows); two&selfcalls made while a&mutborrow was live (hoisted); and one block indelete_forward_commandthat had been copied from the insert-text path and still referenced itsinputvariable, withreturn;in a-> boolfunction. Forward-delete now replaces the selection withString::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:
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 withSIGABRT, and because it is a stack overflow rather than a panic,catch_unwindcannot contain it. Not exploitable here, verified rather than assumed: the advisory coversDocument::load*, and this crate never parses a PDF.arch_pdf.rsonly writes them, there is nolopdfimport anywhere in the workspace, and no PDF read path exists. No fix is available in range either —printpdf 0.7pinslopdf 0.31andcargo update -p lopdfmoves 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
licensefield. Handled withoutallow-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.