chore: scaffold nine-crate cargo workspace with headless selftest gate
Empty-but-compiling crates enforce the architecture from day one: forbid(unsafe_code) everywhere except vernier-occt-sys, workspace lints deny missing docs and unwrap/expect, and the downward-only dependency rule is now compiler-checked. vernier-cli --selftest runs headless and its output is asserted byte-identical across runs (invariant #1). system-occt is a declared no-op feature so the documented fast-iteration commands work before the Phase 1 bridge lands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+39
@@ -0,0 +1,39 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "vernier-app"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "vernier-cli"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "vernier-doc"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "vernier-kernel"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "vernier-occt-sys"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "vernier-render"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "vernier-solver"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "vernier-tess"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "vernier-ui"
|
||||
version = "0.0.1"
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/*"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.1"
|
||||
edition = "2024"
|
||||
rust-version = "1.96"
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://git.briggen.dev/NilsBriggen/vernier"
|
||||
publish = false
|
||||
|
||||
[workspace.dependencies]
|
||||
indexmap = "2"
|
||||
thiserror = "2"
|
||||
tracing = "0.1"
|
||||
|
||||
# Deny-by-default discipline from CLAUDE.md. Test code may locally
|
||||
# `#[allow(clippy::unwrap_used)]` — production code may not.
|
||||
[workspace.lints.rust]
|
||||
missing_docs = "deny"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
unwrap_used = "deny"
|
||||
expect_used = "deny"
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "vernier-app"
|
||||
description = "VernierCAD desktop application"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[features]
|
||||
# Forward to vernier-occt-sys/system-occt once the kernel dependency chain
|
||||
# reaches this crate (Phase 1). Declared now so
|
||||
# `cargo run -p vernier-app --features system-occt` works as documented.
|
||||
system-occt = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,9 @@
|
||||
//! Thin `main()` for the VernierCAD desktop application.
|
||||
//!
|
||||
//! Everything of substance lives in the library crates; this binary only
|
||||
//! wires them together. The egui/wgpu shell arrives with the Phase 0 render
|
||||
//! milestone.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "vernier-cli"
|
||||
description = "Headless driver for scripting and testing"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,32 @@
|
||||
//! Headless driver for VernierCAD — the primary verification path.
|
||||
//!
|
||||
//! Every modelling operation must be drivable through this binary with no
|
||||
//! window, no GPU, and no display, producing a deterministic artifact that
|
||||
//! can be asserted on. A feature that cannot be exercised here is not
|
||||
//! finished.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
match args.first().map(String::as_str) {
|
||||
Some("--selftest") => selftest(),
|
||||
_ => {
|
||||
eprintln!("usage: vernier-cli --selftest");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs every registered self-check headlessly and prints a JSON report to
|
||||
/// stdout. The report must be byte-identical across runs (invariant #1).
|
||||
///
|
||||
/// Phase 0 gate: passes with no display attached. Subsystem checks register
|
||||
/// here as they land; an empty run still proves the binary works headless.
|
||||
fn selftest() -> ExitCode {
|
||||
let report = r#"{"selftest":"pass","checks":[]}"#;
|
||||
println!("{report}");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//! The Phase 0 gate: `--selftest` passes with no display, deterministically.
|
||||
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use std::process::{Command, Output};
|
||||
|
||||
fn run_selftest() -> Output {
|
||||
Command::new(env!("CARGO_BIN_EXE_vernier-cli"))
|
||||
.arg("--selftest")
|
||||
.env_remove("DISPLAY")
|
||||
.env_remove("WAYLAND_DISPLAY")
|
||||
.output()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selftest_passes_headless() {
|
||||
let out = run_selftest();
|
||||
assert!(out.status.success(), "selftest exited non-zero");
|
||||
let report = String::from_utf8(out.stdout).unwrap();
|
||||
assert!(report.contains(r#""selftest":"pass""#), "report: {report}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selftest_output_is_deterministic() {
|
||||
let first = run_selftest();
|
||||
let second = run_selftest();
|
||||
assert_eq!(
|
||||
first.stdout, second.stdout,
|
||||
"selftest output differs between runs — invariant #1 violated"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "vernier-doc"
|
||||
description = "Document model: feature DAG, EntityId space, undo stack, serialization"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,7 @@
|
||||
//! Document model: feature DAG, `EntityId` allocation, undo stack, serialization.
|
||||
//!
|
||||
//! `EntityId`s are allocated here and mapped through the kernel façade —
|
||||
//! entity identity never derives from an index or a pointer. Like
|
||||
//! `vernier-solver`, this crate must never depend on `vernier-kernel`.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "vernier-kernel"
|
||||
description = "Kernel abstraction: trait Kernel, OpResult, EntityId"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,8 @@
|
||||
//! Kernel abstraction: `trait Kernel`, `OpResult`, `EntityId`.
|
||||
//!
|
||||
//! Every kernel operation returns a shape *plus a history delta* describing
|
||||
//! which entities were generated, modified, or deleted — an operation that
|
||||
//! returns a bare shape is incomplete. The OCCT implementation lives behind
|
||||
//! this boundary so hot paths can be replaced later without touching callers.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "vernier-occt-sys"
|
||||
description = "cxx bridge and C++ facade to the OCCT geometry kernel"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[features]
|
||||
# Build against the distro OCCT package instead of the vendored submodule.
|
||||
# No-op until the Phase 1 bridge lands; declared now so the fast-iteration
|
||||
# commands documented in CLAUDE.md work from day one.
|
||||
system-occt = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,9 @@
|
||||
//! FFI bridge to OCCT via a thin C++ façade with opaque `u64` shape handles.
|
||||
//!
|
||||
//! This is the **only** crate in the workspace permitted `unsafe`. The façade
|
||||
//! owns a shape registry on the C++ side; OCCT's `Handle<T>` never crosses the
|
||||
//! FFI boundary, and every OCCT exception is caught in C++ and converted to a
|
||||
//! result code before it can unwind into Rust.
|
||||
//!
|
||||
//! The bridge itself arrives in Phase 1. `facade.hpp` will be the complete API
|
||||
//! surface.
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "vernier-render"
|
||||
description = "wgpu renderer with ID-buffer picking"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,8 @@
|
||||
//! wgpu renderer: viewport, camera, ID-buffer picking.
|
||||
//!
|
||||
//! The render thread is sacred — it never blocks on the kernel. Kernel work
|
||||
//! runs on a worker; the viewport orbits at refresh rate during an
|
||||
//! eight-second recompute or the design is wrong. Target GPU is AMD RDNA3 via
|
||||
//! Mesa/RADV (Vulkan); never assume NVIDIA.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "vernier-solver"
|
||||
description = "2D sketch constraint solver (sparse Jacobian, Levenberg-Marquardt)"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,8 @@
|
||||
//! 2D sketch constraint solver: sparse Jacobian + Levenberg–Marquardt.
|
||||
//!
|
||||
//! Depends on **nothing** from the kernel layer, by design — that is what
|
||||
//! keeps it fast to test and small enough to reason about in one sitting.
|
||||
//! If a task here seems to need `vernier-kernel`, the abstraction is wrong;
|
||||
//! raise it rather than adding the dependency.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "vernier-tess"
|
||||
description = "Tessellation and tessellation cache"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
//! Tessellation of kernel shapes into render meshes, plus the tessellation cache.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "vernier-ui"
|
||||
description = "egui shell: viewport widget, timeline"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,7 @@
|
||||
//! egui application shell: viewport widget, timeline, panels.
|
||||
//!
|
||||
//! egui sits behind an abstraction so it can be swapped later without
|
||||
//! touching the rest of the stack. Degrees exist only at this boundary —
|
||||
//! everything below works in radians and millimetres.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env fish
|
||||
# Full check — must pass before any commit.
|
||||
|
||||
cargo fmt --check
|
||||
and cargo clippy --all-targets -- -D warnings
|
||||
and cargo test --workspace
|
||||
and cargo run -q -p vernier-cli -- --selftest
|
||||
Reference in New Issue
Block a user