name: cad # CORE-00 — truthful CAD CI, owned by the CAD crates. # # Previously CAD gates lived inside `nigig-build.yml` aimed at # `crates/apps/nigig-build/src/construction_frame/pages/workspace/cad`, # a tree that no longer exists. A grep over a missing directory exits # non-zero inside `if`, so every one of those gates reported "OK" while # scanning nothing. This workflow scans the real trees # (`crates/apps/cad/cad-core`, `crates/apps/cad/cad-ui`) and every gate # below fails when its target set is empty -- an empty scan is never green. # # Known-red policy: the `cad-ui` demo semantic failure was fixed # legitimately by the UI-02 kind_hint fix (see IGNORED_TESTS.md demo # triage) and `cargo test -p cad-core` needs the pinned toolchain. # What stays visible: any semantic failure must stay visible; this # workflow must not be weakened to recover green. on: push: paths: - 'crates/apps/cad/**' - 'tools/test-cad-coverage.sh' - 'tools/test-cad-widget-coverage.sh' - 'Cargo.lock' - 'Cargo.toml' - 'rust-toolchain.toml' - '.forgejo/workflows/cad.yml' - 'crates/apps/cad/cad-ui/IGNORED_TESTS.md' pull_request: paths: - 'crates/apps/cad/**' - 'tools/test-cad-coverage.sh' - 'tools/test-cad-widget-coverage.sh' - 'Cargo.lock' - 'Cargo.toml' - 'rust-toolchain.toml' - '.forgejo/workflows/cad.yml' - 'crates/apps/cad/cad-ui/IGNORED_TESTS.md' # Explicit non-zero budget for ignored CAD tests. Bumping this number # requires a tranche note with owner, reason, and expiry (UI-00 owns the # inventory). A drift in either direction fails: silently adding ignores # hides coverage, silently dropping the count means this budget is stale. env: CAD_IGNORED_BUDGET: 20 jobs: # Fast gates, no toolchain. A red job here means the scan itself is # dishonest -- fix the scan, never the target count. cad-truth-gates: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 # Every source scan below must assert it scanned at least one owned # file. This helper is the single definition of "non-empty"; the # empty-fixture proof at the end of this job exercises it. - name: CAD source roots are non-empty run: | set -euo pipefail scan_rs() { local dir="$1" local n n=$(find "$dir" -name '*.rs' | wc -l) if [ "$n" -eq 0 ]; then echo "ERROR: no Rust sources under $dir -- the scan target is empty." return 1 fi echo "OK: $dir ($n files)" } scan_rs crates/apps/cad/cad-core/src scan_rs crates/apps/cad/cad-ui/src # The removed tree must not be scanned as if it still existed. Each # live reference needs a guard on the CAD variable/dir itself (added # by CORE-00) until BUILD-00 removes the stale gates outright. The # match is deliberately narrow: an unrelated `test -f` elsewhere in # the file does not count as guarding the CAD scan. - name: Stale CAD tree references fail closed run: | set -euo pipefail stale='nigig-build/src/construction_frame/pages/workspace/cad' fail=0 # cad.yml itself is excluded: it names the removed tree only to # forbid scanning it, and never scans it. Comment-only lines are # stripped like the nigig-build.yml gates do, so documentation # cannot trip the gate. while IFS= read -r ref; do file="${ref%%:*}" [ "$file" = ".forgejo/workflows/cad.yml" ] && continue code="$(echo "$ref" | sed 's/^[^:]*:[0-9]*://;s/^[[:space:]]*//')" case "$code" in \#*) continue ;; esac if ! grep -qE 'test -d "\$cad"|test -f "\$f"|os\.path\.isdir\(CAD|\[\[ ! -d "\$CAD"|\[\[ ! -d "\$ROOT/\$CAD_REL"' "$file"; then echo "UNGUARDED stale reference: $ref" fail=1 fi done < <(grep -rn --include='*.yml' --include='*.sh' "$stale" .forgejo/workflows tools || true) if [ "$fail" -ne 0 ]; then echo echo "ERROR: the reference(s) above scan a removed tree with no" echo "missing-dir guard, so the gate passes over an empty set." echo "Guard it (fail closed) or remove it under BUILD-00." exit 1 fi echo "OK: all stale-tree references are guarded" # Ignored tests are a budget, not background noise. - name: Ignored-test budget is exact run: | set -euo pipefail n=$(grep -rn '#\[ignore' crates/apps/cad/cad-core/src crates/apps/cad/cad-ui/src | wc -l) if [ "$n" -ne "$CAD_IGNORED_BUDGET" ]; then echo "ERROR: $n ignored CAD tests, budget is $CAD_IGNORED_BUDGET." echo "Update CAD_IGNORED_BUDGET with a tranche note (owner, reason, expiry)." exit 1 fi echo "OK: ignored CAD tests = $n (budget $CAD_IGNORED_BUDGET)" # UI-00: every true #[ignore] must be named in the inventory with # owner, reason, and expiry. The budget gate above counts grep hits # (20 = 19 attributes + 1 doc-comment mention); this gate checks # the 19 attribute owners actually document their test. - name: Ignore inventory names every ignored test run: | set -euo pipefail inv=crates/apps/cad/cad-ui/IGNORED_TESTS.md test -f "$inv" || { echo "ERROR: $inv is missing."; exit 1; } fail=0 while IFS= read -r line; do fn="$(echo "$line" | sed -n 's/.*fn \([A-Za-z0-9_]*\)(.*/\1/p')" [ -n "$fn" ] || continue if ! grep -q "$fn" "$inv"; then echo "UNINVENTORIED ignored test: $fn ($line)" fail=1 fi done < <(grep -rn -A1 '#\[ignore' crates/apps/cad/cad-core/src crates/apps/cad/cad-ui/src | grep -E 'fn [A-Za-z0-9_]+\(' || true) if [ "$fail" -ne 0 ]; then echo echo "ERROR: ignored test(s) above are not named in $inv." echo "Add owner, reason, and expiry there in the same tranche." exit 1 fi echo "OK: all ignored test fns are inventoried" # UI-00: an expiry in the past fails. Extensions are reviewable # edits to IGNORED_TESTS.md, never silent CI edits. Only the # pipe-table expiry column is scanned, so the header date never # trips the gate. - name: No ignore expiry has passed run: | set -euo pipefail inv=crates/apps/cad/cad-ui/IGNORED_TESTS.md today=$(date +%F) fail=0 while IFS= read -r d; do d="$(echo "$d" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}')" if [[ "$d" < "$today" ]]; then echo "EXPIRED ignore entry: $d (today is $today)" fail=1 fi done < <(grep -E '^\| [0-9]+ \|' "$inv" | grep -oE '\| [0-9]{4}-[0-9]{2}-[0-9]{2}' || true) if [ "$fail" -ne 0 ]; then echo echo "ERROR: at least one ignore expiry has passed." echo "Re-triage, convert to a nightly/device job, or extend with reason." exit 1 fi echo "OK: no ignore expiry has passed (today $today)" # UI-00 demo triage, fixed legitimately by the UI-02 kind_hint # fix: demo_has_slab_and_wall failed because domain_box() never # set kind_hint, so wall+slab both classified as Cube and # demo_counts() returned (0,0). The test stays as a regression # guard; this gate forbids hiding it by deletion, inversion, or # #[ignore]. - name: Demo semantic failure is preserved run: | set -euo pipefail demo=crates/apps/cad/cad-ui/src/demo.rs test -f "$demo" || { echo "ERROR: $demo is missing."; exit 1; } grep -q 'fn demo_has_slab_and_wall' "$demo" \ || { echo "ERROR: demo_has_slab_and_wall test was deleted."; exit 1; } grep -q 'assert!(walls >= 1' "$demo" \ || { echo "ERROR: walls >= 1 assertion was removed or inverted."; exit 1; } grep -q 'assert!(slabs >= 1' "$demo" \ || { echo "ERROR: slabs >= 1 assertion was removed or inverted."; exit 1; } if grep -q -B3 'fn demo_has_slab_and_wall' "$demo" | grep -q '#\[ignore'; then echo "ERROR: demo failure was hidden behind #[ignore]." exit 1 fi echo "OK: demo semantic test is preserved (regression guard)" # UI-01 capability containment: STEP, F12 capture, render2d, and the # RayTrace shading slot are contained by the matrix in # `cad-ui/src/capabilities.rs`. Every sub-check below fails with the # file that reintroduces a reachable misleading action. Like the # UI-00 gates, these are static (no toolchain): they pin the exact # labels, predicates, and dispatch paths one matrix row owns. - name: UI-01 contained capabilities stay contained run: | set -euo pipefail ui=crates/apps/cad/cad-ui/src fail=0 say() { echo "$1"; fail=1; } # 1. The matrix exists and names every contained capability once. for cap in StepExport ImageCapture Render2dScript RayTraceMode XrayMode TwodPersistence; do grep -q "$cap" "$ui/capabilities.rs" \ || say "MISSING matrix entry: $cap (capabilities.rs)" done # 2. Default builds offer no reachable STEP action: the button # carries the contained label, dispatch refuses via the matrix, # and the only opt-in is the non-default cargo feature. grep -q 'text: "STEP (off)"' "$ui/lib.rs" \ || say "STEP button lost its contained label (lib.rs)" if grep -n 'text: "STEP"' "$ui/lib.rs" | grep -v 'STEP (off)' | grep -v 'STEP (EXP' | grep -q .; then say "bare STEP button label reintroduced (lib.rs)" fi grep -q 'step_export_enabled' "$ui/workspace.rs" \ || say "export_step bypasses the capability matrix (workspace.rs)" grep -q 'sync_capability_labels' "$ui/workspace_actions.rs" \ || say "export dispatch lost its matrix label sync (workspace_actions.rs)" grep -q 'experimental-step' crates/apps/cad/cad-ui/Cargo.toml \ || say "experimental-step feature missing (cad-ui/Cargo.toml)" if grep -rn 'experimental-step' "$ui" --include='*.rs' -l | grep -v -q -e 'capabilities.rs' -e 'workspace.rs'; then say "experimental-step referenced outside capabilities.rs/workspace.rs" fi # 3. F12 writes no pixels: the synthetic gradient is gone from # dispatch, and palette + keymap carry disabled copy. if grep -q 'sky-to-ground' "$ui/workspace.rs"; then say "synthetic F12 gradient reintroduced (workspace.rs)" fi if grep -q 'write_render_png' "$ui/workspace.rs"; then say "F12 dispatch writes pixels again (workspace.rs)" fi grep -q 'Render High-Res Image (disabled' "$ui/command_palette.rs" \ || say "palette lost its F12 disabled copy (command_palette.rs)" grep -q 'F12.*disabled' "$ui/keymap.rs" \ || say "keymap lost its F12 disabled copy (keymap.rs)" # 4. render2d fails loudly: the binding records the call and eval # converts it into a deterministic error (never silent 0.0). grep -q 'RENDER2D_CALLED' "$ui/script_bindings.rs" \ || say "render2d containment flag missing (script_bindings.rs)" grep -q 'took_render2d_call' "$ui/script_bindings.rs" \ || say "render2d eval error missing (script_bindings.rs)" # 5. RayTrace slot is unreachable: dropdown index + palette cycle # coerce through the matrix, and every label names the containment. grep -q 'coerce_render_mode_index' "$ui/viewport.rs" \ || say "dropdown coercion missing (viewport.rs)" grep -q 'next_shading_index' "$ui/workspace.rs" \ || say "palette cycle skips nothing (workspace.rs)" grep -q 'Ray Trace (disabled)' "$ui/lib.rs" \ || say "desktop render-mode label lost containment (lib.rs)" grep -q 'Ray (off)' "$ui/lib.rs" \ || say "mobile render-mode label lost containment (lib.rs)" grep -q 'Ray (off)' "$ui/viewport_header.rs" \ || say "header Ray label lost containment (viewport_header.rs)" # 6. X-ray stays visibly experimental wherever it appears. grep -q 'X-Ray (exp)' "$ui/lib.rs" \ || say "X-Ray button lost its experimental label (lib.rs)" if [ "$fail" -ne 0 ]; then echo echo "ERROR: UI-01 containment regressed (see lines above)." echo "Restore the matrix-driven label/dispatch, never the old action." exit 1 fi echo "OK: UI-01 containment holds (STEP/F12/render2d/RayTrace/X-ray)" # UI-02 session authority: identity, revision, and id supply are # owned once by `cad-ui/src/session_controller.rs`, with the # checked allocator in `scene_holder.rs`. Every sub-check below # fails with the file that reintroduces a second authority, a # public mutable parts vector, or an unchecked id path. Like the # UI-00/UI-01 gates, these are static (no toolchain). - name: UI-02 session authority stays singular run: | set -euo pipefail ui=crates/apps/cad/cad-ui/src fail=0 say() { echo "$1"; fail=1; } # 1. The controller exists and names every authority symbol. test -f "$ui/session_controller.rs" \ || say "missing session controller (session_controller.rs)" for sym in SessionId ProjectId DocumentId Revision DocumentDescriptor ControllerError CadSessionController try_reserve_up_to; do grep -q "$sym" "$ui/session_controller.rs" \ || say "MISSING authority symbol: $sym (session_controller.rs)" done grep -q 'pub mod session_controller' "$ui/lib.rs" \ || say "controller not wired into the crate (lib.rs)" # 2. The checked allocator exists next to the legacy one. grep -q 'try_allocate' "$ui/scene_holder.rs" \ || say "checked allocator missing (scene_holder.rs)" grep -q 'enum AllocError' "$ui/scene_holder.rs" \ || say "AllocError missing (scene_holder.rs)" # 3. The dead parallel authority stays deleted. if [ -f "$ui/document.rs" ]; then say "dead document.rs authority reintroduced (delete it, route through the controller)" fi # 4. No public mutable parts vector: the canonical store keeps # its nodes private and mutation goes through methods. if grep -rn 'pub nodes' "$ui" --include='*.rs' | grep -q .; then say "public mutable node vector reintroduced (keep nodes private)" fi # 5. The only whole-vec escape hatch stays test-confined. if ! grep -B1 'fn as_mut_vec_without_bump' "$ui/scene_holder.rs" | grep -q 'cfg(test)'; then say "as_mut_vec_without_bump lost its test-only confinement (scene_holder.rs)" fi if [ "$fail" -ne 0 ]; then echo echo "ERROR: UI-02 session authority regressed (see lines above)." echo "Restore the single controller, never a second authority." exit 1 fi echo "OK: UI-02 session authority holds (identity/revision/checked ids)" # UI-03a project repository: the filesystem side names no # production path (every function takes an injected root), ids # cross as validated slugs, writes are atomic, and opening # returns an explicit outcome. Like the earlier gates, these are # static (no toolchain). - name: UI-03a project repository stays root-injected run: | set -euo pipefail ui=crates/apps/cad/cad-ui/src fail=0 say() { echo "$1"; fail=1; } # 1. The repository exists and names every protocol symbol. test -f "$ui/project_repo.rs" \ || say "missing project repository (project_repo.rs)" for sym in RepoRoot ProjectSlug ProjectManifest OpenOutcome RepoError SaveOptions FailPoint save_bytes_atomic SCHEMA_VERSION; do grep -q "$sym" "$ui/project_repo.rs" \ || say "MISSING repository symbol: $sym (project_repo.rs)" done grep -q 'pub mod project_repo' "$ui/lib.rs" \ || say "repository not wired into the crate (lib.rs)" # 2. No ambient production path: no store dir, no app-data # dir, no thread-local active project, no store globals. if grep -n 'store_dir\|app_data_dir\|ACTIVE_PROJECT\|get_active_project\|cad_projects_dir\|cad_store::\|project_store::' "$ui/project_repo.rs" | grep -q .; then say "ambient production path in the repository (project_repo.rs must take only injected roots)" fi # 3. Slugs stay the only path identity: no raw-id path join. if grep -n 'format!("{}' "$ui/project_repo.rs" | grep -v 'LEGACY_EXTENSION\|tmp-\|display()\|{reason}\|{e}\|{id\|{point\|{slug}\|{other\|{msg}\|{bad\|{ok}\|{stray' | grep -q .; then say "unvalidated id reaches a path join (project_repo.rs)" fi if [ "$fail" -ne 0 ]; then echo echo "ERROR: UI-03a repository regressed (see lines above)." echo "Restore injected roots and validated slugs, never ambient paths." exit 1 fi echo "OK: UI-03a repository holds (roots/slugs/atomic/outcomes)" # CORE-01..12 canonical core: structured errors, budgets, checked # ids, graph/transforms, versioned document, polygon pipeline, mesh # hardening, atomic edits, world-mesh stream, bounded STL/DXF, # quarantined STEP, exact URL policy. Static (no toolchain). - name: CORE canonical modules stay complete run: | set -euo pipefail core=crates/apps/cad/cad-core/src fail=0 say() { echo "$1"; fail=1; } for mod in error budgets checked_ids graph document polygon mesh_validate edit world_mesh url_policy; do test -f "$core/$mod.rs" \ || say "missing CORE module: $mod.rs" grep -q "CORE-0\|CORE-1" "$core/$mod.rs" 2>/dev/null \ || say "module $mod.rs lost its tranche header" done for sym in CadError ErrorKind ValidationPolicy GeometryBudget CheckedAllocator ExportIdAllocator RemapTable validate_graph world_matrix effective_visibility CadDocument EntityKind LengthUnit validate_polygon triangulate_profile validate_mesh checked_subdivide DocumentEdit ScenePatch stream_world_mesh classify_url; do if ! grep -rq "$sym" "$core" --include='*.rs'; then say "MISSING CORE symbol: $sym" fi done grep -q 'pub mod error' "$core/lib.rs" || say "core modules not wired (lib.rs)" # STEP quarantine: feature exists, default refuses, no hash dedup. grep -q 'experimental-step' crates/apps/cad/cad-core/Cargo.toml \ || say "experimental-step feature missing (cad-core/Cargo.toml)" grep -q 'quarantined (experimental-step feature is off)' "$core/arch_step.rs" \ || say "STEP default refusal missing (arch_step.rs)" grep -q 'ExperimentalStepExporter' "$core/arch_step.rs" \ || say "ExperimentalStep alias missing (arch_step.rs)" if grep -q 'quantize(v: &Vec3d) -> u64' "$core/arch_step.rs"; then say "hash vertex dedup reintroduced (arch_step.rs)" fi grep -q 'OPEN_SHELL' "$core/arch_step.rs" \ || say "honest open-shell path missing (arch_step.rs)" # URL trust: no prefix classification anywhere near a decision. if grep -rn 'starts_with("http://127' "$core" --include='*.rs' | grep -q .; then say "prefix URL classification reintroduced (use url_policy)" fi # Identity transform means identity (scale 1, not 0). grep -q 'refusing to wrap\|scale: 1.0' "$core/cad_scene.rs" \ || say "identity-transform contract weakened (cad_scene.rs)" if [ "$fail" -ne 0 ]; then echo echo "ERROR: CORE canonical modules regressed (see lines above)." exit 1 fi echo "OK: CORE canonical modules hold (errors/budgets/ids/graph/doc/mesh/urls)" # UI-03b..15 session modules: switch transactions, rebuild, journal, # sandbox, AI correlation, bounded caches, valid BVH, one coordinate # model, real capture, bounded exports, honest formats, lifecycle. - name: UI session modules stay complete run: | set -euo pipefail ui=crates/apps/cad/cad-ui/src fail=0 say() { echo "$1"; fail=1; } for mod in session_switch rebuild journal script_sandbox ai mesh_cache coords capture export_coordinator lifecycle; do test -f "$ui/$mod.rs" \ || say "missing UI module: $mod.rs" done for sym in SwitchableState switch_project RebuildCoordinator CommandJournal ScriptBudgets PreviewBuffer RevisionedCache PlaneBasis CaptureRequest ExportCoordinator LifecycleGate; do if ! grep -rq "$sym" "$ui" --include='*.rs'; then say "MISSING UI symbol: $sym" fi done # BVH: permutation + validator, no direct-prims leaf walk. grep -q 'order: Vec' "$ui/bvh.rs" \ || say "BVH permutation missing (bvh.rs)" grep -q 'pub fn validate' "$ui/bvh.rs" \ || say "BVH structural validator missing (bvh.rs)" if grep -q 'self.prims\[node.first' "$ui/bvh.rs"; then say "direct-prims leaf walk reintroduced (bvh.rs must go through order)" fi # AI backend correctness: worker takes the selected backend and # the local path fails closed without an endpoint. grep -q 'fn new(_cx: &mut Cx, backend: BackendType)' "$ui/lib.rs" \ || say "AI worker lost backend selection (lib.rs)" grep -q 'local_openai_url().is_none()' "$ui/lib.rs" \ || say "local fail-closed path missing (lib.rs)" # Streaming preview must not write the editor (the apply path # in apply_ai_response is the only editor writer for AI text). if grep -n -A12 'fn stream_ai_response_to_editor' "$ui/workspace.rs" | grep -q 'set_editor_text_all'; then say "destructive streaming reintroduced (workspace.rs preview must not write the editor)" fi # Export bound: dispatch refuses past the ceiling. grep -q 'MAX_EXPORTS_IN_FLIGHT' "$ui/exporters.rs" \ || say "export ceiling missing (exporters.rs)" grep -q 'MAX_EXPORTS_IN_FLIGHT' "$ui/workspace.rs" \ || say "export dispatch lost its bound (workspace.rs)" # Script sandbox: source ceiling before the VM. grep -q 'check_source' "$ui/script_bindings.rs" \ || say "script source preflight missing (script_bindings.rs)" if [ "$fail" -ne 0 ]; then echo echo "ERROR: UI session modules regressed (see lines above)." exit 1 fi echo "OK: UI session modules hold (switch/rebuild/journal/sandbox/ai/cache/bvh/coords/capture/exports/lifecycle)" - name: Empty-target fixture proves gates fail run: | set -euo pipefail empty=$(mktemp -d) if find "$empty" -name '*.rs' | grep -q .; then echo "ERROR: fresh temp dir is not empty -- fixture broken." exit 1 fi if [ "$(find "$empty" -name '*.rs' | wc -l)" -ne 0 ]; then echo "ERROR: empty scan reported sources -- predicate broken." exit 1 fi echo "OK: empty fixture scans as empty (a gate over it would fail, not pass)" rm -rf "$empty" - name: Reject whitespace errors run: git diff --check # Exact package by Cargo package ID -- never a copied source harness. cad-core-checks: runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v4 - name: Install native dependencies run: | sudo apt-get update -qq sudo apt-get install -y -qq \ pkg-config libwayland-dev libxcursor-dev libxrandr-dev \ libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \ libglib2.0-dev libssl-dev libsqlite3-dev libudev-dev \ libpulse-dev libxkbcommon-dev - name: Install the declared toolchain run: | set -e version="$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ rust-toolchain.toml | head -n 1)" curl --fail --location --proto '=https' --tlsv1.2 https://sh.rustup.rs -o /tmp/rustup-init chmod 700 /tmp/rustup-init /tmp/rustup-init -y --profile minimal --default-toolchain "$version" --no-modify-path echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Formatting (cad-core) run: cargo fmt -p cad-core -- --check - name: Check (cad-core) run: cargo check --locked -p cad-core --all-targets - name: Test (cad-core) run: cargo test --locked -p cad-core --all-targets -- --test-threads=1 - name: Clippy (cad-core) run: cargo clippy --locked -p cad-core --all-targets -- -D warnings # Direct consumers of the public model. A red consumer here is a # contract break or a known UI-00 baseline failure -- visible, not hidden. # # UI-00 lane split: pure library, integration, and real runtime UI # results are recorded as SEPARATE artifacts so one lane cannot hide # behind another's total. The lib lane was expected-red at the UI-00 # baseline (demo.rs wall-classification failure, since fixed by the # UI-02 kind_hint fix); the integration lane runs the UI-15 matrix; # the runtime lane proves the standalone binary compiles. cad-consumers: runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v4 - name: Install native dependencies run: | sudo apt-get update -qq sudo apt-get install -y -qq \ pkg-config libwayland-dev libxcursor-dev libxrandr-dev \ libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \ libglib2.0-dev libssl-dev libsqlite3-dev libudev-dev \ libpulse-dev libxkbcommon-dev - name: Install the declared toolchain run: | set -e version="$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ rust-toolchain.toml | head -n 1)" curl --fail --location --proto '=https' --tlsv1.2 https://sh.rustup.rs -o /tmp/rustup-init chmod 700 /tmp/rustup-init /tmp/rustup-init -y --profile minimal --default-toolchain "$version" --no-modify-path echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Check (cad-ui) run: cargo check --locked -p cad-ui --all-targets # Lane 1: pure library unit tests (in-file #[cfg(test)]). # Was expected-red at the UI-00 baseline (demo_has_slab_and_wall # failed; fixed legitimately by the UI-02 kind_hint fix — see # IGNORED_TESTS.md demo triage). The log is kept as its own # artifact so any failure is inspectable, not a bare red step. - name: Test (cad-ui lib lane) run: | set -euo pipefail mkdir -p cad-artifacts set +e cargo test --locked -p cad-ui --lib -- --test-threads=1 2>&1 | tee cad-artifacts/cad-ui-lib.log status=${PIPESTATUS[0]} set -e echo "lib lane exit: $status" | tee -a cad-artifacts/cad-ui-lib.log exit "$status" # Lane 2: integration tests (tests/ targets). UI-15 owns the # runtime/migration matrix (project_lifecycle, script_limits, # bvh_differential, export_interop, runtime_ui); CORE-12 owns # cad-core's (resource_limits, format_interop, migration_corpus). # An empty lane is recorded as empty, never as green coverage. - name: Test (cad-ui integration lane) run: | set -euo pipefail mkdir -p cad-artifacts if ls crates/apps/cad/cad-ui/tests/*.rs >/dev/null 2>&1; then cargo test --locked -p cad-ui --tests -- --test-threads=1 2>&1 | tee cad-artifacts/cad-ui-integration.log else echo "cad-ui integration lane: no tests/*.rs targets exist yet (UI-15 owns runtime/migration matrix)." | tee cad-artifacts/cad-ui-integration.log echo "This empty lane is recorded, not counted as coverage." | tee -a cad-artifacts/cad-ui-integration.log fi # Lane 3: real runtime UI — the standalone binary must compile. # Headless CI cannot run the Makepad event loop; this lane proves # the binary target builds and records which binary was built. # Runtime behavior matrix itself is owned by UI-15. - name: Build (cad-ui runtime lane) run: | set -euo pipefail mkdir -p cad-artifacts cargo check --locked -p cad-ui --bins 2>&1 | tee cad-artifacts/cad-ui-runtime.log echo "--- bins ---" | tee -a cad-artifacts/cad-ui-runtime.log ls crates/apps/cad/cad-ui/src/bin/ | tee -a cad-artifacts/cad-ui-runtime.log - name: Upload cad-ui lane artifacts if: always() uses: actions/upload-artifact@v4 with: name: cad-ui-lanes path: cad-artifacts/ if-no-files-found: error - name: Check (nigig-build) run: cargo check --locked -p nigig-build --all-targets