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

264 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<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:
```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.