diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index d39a437..b750958 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -33,12 +33,12 @@ body: id: stack_trace attributes: label: Stack Trace / Error Logs - description: paste full stack trace or logs + description: Provide full stack trace or logs - type: input id: version attributes: label: Version / Commit - description: git SHA or package version + description: Provide git SHA or package version - type: checkboxes id: env attributes: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58e148b..cd9b19f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,6 +57,25 @@ jobs: - name: PRNG golden regression (rmg-core) run: cargo test -p rmg-core --features golden_prng -- tests::next_int_golden_regression + test-musl: + name: Tests (musl) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: false + - uses: dtolnay/rust-toolchain@1.90.0 + with: + targets: x86_64-unknown-linux-musl + - name: Install musl tools + run: sudo apt-get update && sudo apt-get install -y musl-tools + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + . + - name: cargo test (rmg-core, musl) + run: cargo test -p rmg-core --target x86_64-unknown-linux-musl + docs: name: Docs Guard runs-on: ubuntu-latest diff --git a/.github/workflows/macos-local.yml b/.github/workflows/macos-local.yml new file mode 100644 index 0000000..77f0133 --- /dev/null +++ b/.github/workflows/macos-local.yml @@ -0,0 +1,24 @@ +name: CI (macOS — manual) + +on: + workflow_dispatch: + +jobs: + test-macos: + name: Tests (macOS local only) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: false + - uses: dtolnay/rust-toolchain@1.90.0 + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + . + - name: cargo fmt + run: cargo fmt --all -- --check + - name: cargo clippy + run: cargo clippy --all-targets -- -D warnings -D missing_docs + - name: cargo test (workspace) + run: cargo test --workspace diff --git a/Cargo.lock b/Cargo.lock index 160776d..07d3f53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,33 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + [[package]] name = "blake3" version = "1.8.2" @@ -71,12 +98,46 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "find-msvc-tools" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "hex" version = "0.4.3" @@ -99,6 +160,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "memchr" version = "2.7.6" @@ -115,12 +188,30 @@ dependencies = [ "walkdir", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -130,6 +221,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.41" @@ -139,6 +255,56 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "rmg-cli" version = "0.1.0" @@ -151,6 +317,7 @@ dependencies = [ "bytes", "hex", "once_cell", + "proptest", "serde", "serde_json", "thiserror", @@ -181,12 +348,37 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.20" @@ -262,6 +454,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -282,12 +487,27 @@ dependencies = [ "syn", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -298,6 +518,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.105" @@ -413,3 +642,29 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/crates/rmg-core/Cargo.toml b/crates/rmg-core/Cargo.toml index 8f87bcd..1f51fc6 100644 --- a/crates/rmg-core/Cargo.toml +++ b/crates/rmg-core/Cargo.toml @@ -23,6 +23,7 @@ once_cell = "1.19" [dev-dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +proptest = { version = "1.5" } [features] default = [] diff --git a/crates/rmg-core/tests/proptest_seed_pinning.rs b/crates/rmg-core/tests/proptest_seed_pinning.rs new file mode 100644 index 0000000..7954d6c --- /dev/null +++ b/crates/rmg-core/tests/proptest_seed_pinning.rs @@ -0,0 +1,72 @@ +#![allow(missing_docs)] +use proptest::prelude::*; +use proptest::test_runner::{Config as PropConfig, RngAlgorithm, TestRng, TestRunner}; + +use rmg_core::{ + decode_motion_payload, encode_motion_payload, make_node_id, make_type_id, ApplyResult, Engine, + GraphStore, NodeRecord, MOTION_RULE_NAME, +}; + +// Demonstrates how to pin a deterministic seed for property tests so failures +// are reproducible across machines and CI. +// +// To re-run with a different seed locally, you can set PROPTEST_SEED, e.g.: +// PROPTEST_SEED=0000000000000000000000000000000000000000000000000000000000000042 cargo test -p rmg-core -- proptest_seed_pinned_motion_updates +// Or update the `SEED_BYTES` below for a committed example. + +#[test] +fn proptest_seed_pinned_motion_updates() { + // Pin a seed for deterministic case generation. Using a small numeric + // value is enough; TestRng::from_seed expects 32 bytes. + const SEED_BYTES: [u8; 32] = [ + 0x42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]; + + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &SEED_BYTES); + let mut runner = TestRunner::new_with_rng(PropConfig::default(), rng); + + // Strategy: finite f32 in a sane range to avoid infinities/NaNs. + let scalar = any::().prop_filter("finite", |v| v.is_finite() && v.abs() < 1.0e6); + let vec3 = prop::array::uniform3(scalar.clone()); + + let prop = (vec3.clone(), vec3).prop_map(|(pos, vel)| (pos, vel)); + + runner + .run(&prop, |(pos, vel)| { + // Build a fresh engine for each case (property tests are short). + let entity = make_node_id("prop-entity"); + let entity_ty = make_type_id("entity"); + + let mut store = GraphStore::default(); + store.insert_node( + entity, + NodeRecord { + ty: entity_ty, + payload: Some(encode_motion_payload(pos, vel)), + }, + ); + + let mut engine = Engine::new(store, entity); + engine + .register_rule(rmg_core::motion_rule()) + .expect("register motion rule"); + + let tx = engine.begin(); + let res = engine.apply(tx, MOTION_RULE_NAME, &entity).expect("apply"); + prop_assert!(matches!(res, ApplyResult::Applied)); + engine.commit(tx).expect("commit"); + + let node = engine.node(&entity).expect("node exists"); + let (new_pos, new_vel) = + decode_motion_payload(node.payload.as_ref().expect("payload")).expect("decode"); + + // Velocity is preserved; position += vel * dt (dt = 1.0). + for i in 0..3 { + prop_assert_eq!(new_vel[i].to_bits(), vel[i].to_bits()); + prop_assert_eq!(new_pos[i].to_bits(), (pos[i] + vel[i]).to_bits()); + } + Ok(()) + }) + .expect("proptest with pinned seed should complete"); +} diff --git a/docs/decision-log.md b/docs/decision-log.md index 6bfb7f8..9a481a2 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -24,6 +24,9 @@ The following entries use a heading + bullets format for richer context. | 2025-10-30 | Templates PR scope | Clean `echo/pr-templates-and-project` to contain only templates + docs notes; remove unrelated files pulled in by merge; fix YAML lint (trailing blanks; quote placeholder) | Keep PRs reviewable and single-purpose; satisfy CI Docs Guard | Easier review; no runtime impact | | 2025-10-30 | Docs lint | Fix MD022 (blank line after headings) in `docs/spec-deterministic-math.md` on branch `echo/docs-math-harness-notes` | Keep markdown lint clean; improve readability | No content change; unblock future docs PRs | | 2025-10-30 | Bug template triage | Add optional `stack_trace` and `version` fields to `.github/ISSUE_TEMPLATE/bug.yml` | Capture logs and version/SHA up front to speed debugging | Better triage signal without burdening reporters | +| 2025-10-30 | Bug template wording | Standardize bug template descriptions to imperative capitalization ("Provide …") | Consistent style and clearer prompts | Improved reporter guidance | +| 2025-10-30 | Proptest seed pinning | Add dev‑dep `proptest` and a pinned‑seed property test for motion rule (`proptest_seed_pinning.rs`) | Establish deterministic, reproducible property tests and document seed‑pinning pattern | Tests‑only; no runtime impact | +| 2025-10-30 | CI matrix | Add musl tests job (rmg-core; x86_64-unknown-linux-musl) and a manual macOS workflow for local runs | Cover glibc + musl in CI while keeping macOS optional to control costs | Determinism coverage improves; CI footprint remains lean | | 2025-10-28 | PR #7 merged | Reachability-only snapshot hashing; ports demo registers rule; guarded ports footprint; scheduler `finalize_tx()` clears `pending`; `PortKey` u30 mask; hooks+CI hardened (toolchain pin, rustdoc fixes). | Determinism + memory hygiene; remove test footguns; pass CI with stable toolchain while keeping rmg-core MSRV=1.68. | Queued follow-ups: #13 (Mat4 canonical zero + MulAssign), #14 (geom train), #15 (devcontainer). | | 2025-10-27 | MWMR reserve gate | Engine calls `scheduler.finalize_tx()` at commit; compact rule id used on execute path; per‑tx telemetry summary behind feature. | Enforce independence and clear active frontier deterministically; keep ordering stable with `(scope_hash, family_id)`. | Toolchain pinned to Rust 1.68; add design note for telemetry graph snapshot replay. | diff --git a/docs/execution-plan.md b/docs/execution-plan.md index 8c0a895..140c050 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -59,6 +59,19 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s - Enhanced `.github/ISSUE_TEMPLATE/bug.yml` with optional fields for `Stack Trace / Error Logs` and `Version / Commit` to improve first‑pass triage quality. +> 2025-10-30 — Bug template wording consistency + +- Standardized description capitalization in bug template to imperative form ("Provide …") for consistency with existing fields. + +> 2025-10-30 — PR-03: proptest seed pinning (tests-only) + +- Added `proptest` as a dev‑dependency in `rmg-core` and a single example test `proptest_seed_pinning.rs` that pins a deterministic RNG seed and validates the motion rule under generated inputs. This demonstrates how to reproduce failures via a fixed seed across CI and local runs (no runtime changes). + +> 2025-10-30 — PR-04: CI matrix (glibc + musl; macOS manual) + +- CI: Added a musl job (`Tests (musl)`) that installs `musl-tools`, adds target `x86_64-unknown-linux-musl`, and runs `cargo test -p rmg-core --target x86_64-unknown-linux-musl`. +- CI: Added a separate macOS workflow (`CI (macOS — manual)`) triggered via `workflow_dispatch` to run fmt/clippy/tests on `macos-latest` when needed, avoiding default macOS runner costs. + > 2025-10-29 — Geom fat AABB midpoint sampling (merge-train)