nigig-org/PHASE0_STATUS.md
Arena Agent 80c3f578dd
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
Payment domain and storage / isolated-payment-tests (push) Has been cancelled
ci(cad): gate nigig-build with cargo-deny (Phase 0.4)
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.
2026-07-28 19:51:37 +00:00

12 KiB
Raw Permalink Blame History

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-checkcargo 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-corematrix_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.rsE0603, 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:

  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:

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.