From 941352ed62f902812baefb08333587a44ed09786 Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Fri, 10 Jul 2026 15:03:20 -0600 Subject: [PATCH] CI + decision-check: cargo duals for all Rust gates Wire cargo test -p decision_cores into decision-check and GHA. Add gates_dual tables (rate-limit, sync, tui-reload, span, clamp). Force system linker so hermetic_cc/zig env does not break rustc. --- .github/workflows/test.yml | 6 +++ crates/decision_cores/BUILD.bazel | 1 + crates/decision_cores/tests/gates_dual.rs | 45 +++++++++++++++++++++++ scripts/decision-check.sh | 20 ++++++++-- specs/GATES.md | 6 ++- 5 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 crates/decision_cores/tests/gates_dual.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dfd7c8e..7081ab8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,12 @@ jobs: - name: Run tests run: bazel test //... --profile="$RUNNER_TEMP/bazel-test-profile.json" + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Rust decision cores + run: env CC=cc RUSTFLAGS='-C linker=/usr/bin/cc' cargo test -p decision_cores + - name: Build run: bazel build //cmd/... --profile="$RUNNER_TEMP/bazel-build-profile.json" diff --git a/crates/decision_cores/BUILD.bazel b/crates/decision_cores/BUILD.bazel index ad17eea..121e70d 100644 --- a/crates/decision_cores/BUILD.bazel +++ b/crates/decision_cores/BUILD.bazel @@ -11,5 +11,6 @@ exports_files([ "src/lib.rs", "src/gates.rs", "tests/log_groups_dual.rs", + "tests/gates_dual.rs", "Cargo.toml", ]) diff --git a/crates/decision_cores/tests/gates_dual.rs b/crates/decision_cores/tests/gates_dual.rs new file mode 100644 index 0000000..533f63a --- /dev/null +++ b/crates/decision_cores/tests/gates_dual.rs @@ -0,0 +1,45 @@ +//! Dual tables: thin `gates::*` wrappers ↔ generated decision modules. +//! Mirrors Go production gate tables (rate-limit, sync-bounds, tui-reload, …). + +use decision_cores::gates; + +#[test] +fn rate_limit_wait_needed_table() { + assert!(!gates::rate_limit_wait_needed(1, true, true)); + assert!(!gates::rate_limit_wait_needed(0, false, true)); + assert!(!gates::rate_limit_wait_needed(0, true, false)); + assert!(gates::rate_limit_wait_needed(0, true, true)); +} + +#[test] +fn accept_jobs_attempt_table() { + assert!(gates::accept_jobs_attempt(2, 0)); // unknown attempt + assert!(gates::accept_jobs_attempt(2, 2)); + assert!(!gates::accept_jobs_attempt(2, 1)); // stale + assert!(!gates::accept_jobs_attempt(3, 2)); +} + +#[test] +fn log_fetch_result_fresh_table() { + assert!(gates::log_fetch_result_fresh(1, 1, 0, 0)); + assert!(!gates::log_fetch_result_fresh(0, 1, 0, 0)); // zero job + assert!(!gates::log_fetch_result_fresh(1, 2, 0, 0)); // wrong job + assert!(!gates::log_fetch_result_fresh(1, 1, 0, 1)); // stale gen +} + +#[test] +fn drop_api_for_runner_twin_table() { + assert!(gates::drop_api_for_runner_twin(1, 1, false)); // drop API + assert!(!gates::drop_api_for_runner_twin(1, 1, true)); // keep runner + assert!(!gates::drop_api_for_runner_twin(1, 0, false)); + assert!(!gates::drop_api_for_runner_twin(2, 1, false)); +} + +#[test] +fn clamp_span_to_parent_contains() { + // child end past parent → clamp end into parent + let (s, e) = gates::clamp_span_to_parent(2, 4, 1, 3); + assert!(s < e); + assert!(s >= 1); + assert!(e <= 3); +} diff --git a/scripts/decision-check.sh b/scripts/decision-check.sh index 26d2683..7543ebb 100755 --- a/scripts/decision-check.sh +++ b/scripts/decision-check.sh @@ -3,13 +3,14 @@ # # Fast path after gate/symbol edits: # 1. Production wire symbols still exist -# 2. Bazel tests tagged decision (*spec duals + codegen up_to_date) -# 3. Optional: TLC for named cores (full + decision for that core) +# 2. Bazel tests tagged decision (*spec duals + Go/Rust up_to_date) +# 3. Rust decision_cores duals when cargo is on PATH +# 4. Optional: TLC for named cores (full + decision for that core) # # Full design TLC for everything: scripts/check-specs.sh # # Usage: -# scripts/decision-check.sh # wires + bazel decision tags +# scripts/decision-check.sh # wires + bazel decision tags + cargo # scripts/decision-check.sh --with-tlc # also TLC every core with decision/ # scripts/decision-check.sh ... # TLC only those cores (implies TLC) @@ -48,6 +49,19 @@ if ! bazel test //... --test_tag_filters=decision --test_output=errors; then fail=1 fi +echo +echo "=== cargo test -p decision_cores ===" +if command -v cargo >/dev/null 2>&1; then + # Force system linker: hermetic_cc/zig from Bazel env breaks rustc link. + # Always override RUSTFLAGS (do not inherit zig flags from the shell). + if ! env CC=cc RUSTFLAGS='-C linker=/usr/bin/cc' \ + cargo test -p decision_cores --quiet; then + fail=1 + fi +else + echo "skip: cargo not on PATH" +fi + if [ "$WITH_TLC" -eq 1 ]; then echo echo "=== TLC (decision cores) ===" diff --git a/specs/GATES.md b/specs/GATES.md index 27bc839..785794d 100644 --- a/specs/GATES.md +++ b/specs/GATES.md @@ -4,9 +4,11 @@ **Race redesign:** full TLC in `specs//*.tla`. **Regen:** `bazel run //tools/decision:update` **Lean check:** `scripts/decision-check.sh` -(`--with-tlc` or `` for TLC; full suite: `scripts/check-specs.sh`) +(wires + decision tags + `cargo test -p decision_cores` when cargo present) +**Full TLC:** `scripts/check-specs.sh` (`--with-tlc` / core names on decision-check) **Cites:** production / decision / pure **symbols** only — never `file.go:line` -(lines rot; GATES + duals are the map). +(lines rot; GATES + duals are the map). +**Rust peer:** `crates/decision_cores` (same Decision.tla; `gates::*` wrappers). | Core | GATES | Production package | Prod→gen SSOT | |------|-------|--------------------|---------------|