diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48d2b409009..9272e95dc94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,6 @@ jobs: - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1.16.0 with: - toolchain: "1.93.0" components: rustfmt cache: false rustflags: "" @@ -77,6 +76,9 @@ jobs: with: fetch-depth: 1 + - name: Rust toolchain pin alignment (rust-toolchain.toml vs rustup.dag) + run: bash scripts/check-rust-toolchain-pin-alignment.sh + - name: Fabrication sentinel ratchet (P0-C) run: bash scripts/check-fabrication-sentinels.sh @@ -108,7 +110,6 @@ jobs: - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1.16.0 with: - toolchain: "1.93.0" components: rustfmt cache: false rustflags: "" @@ -145,7 +146,6 @@ jobs: - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1.16.0 with: - toolchain: "1.93.0" # Integration snapshot tests (`regen_parse` path, lane2d cost lens, …) shell out to `rustfmt`. components: rustfmt, clippy cache: false @@ -192,14 +192,14 @@ jobs: # miss, first compile + link on ubuntu-latest) can spend minutes — keep # headroom beyond a tight 120s; per-test discipline lives in # `tests/common/budgeted.rs` (DEFAULT_BUDGET_MS). - - name: v3 tests (Stage 2d integration module, 300s cold-compile-safe budget) + - name: v3 tests (Stage 2d integration module, 420s cold-compile-safe budget) run: | start=$(date +%s) cargo test -p v3-compiler --test integration lane2_stage_2d_symbolic_cost_test:: elapsed=$(( $(date +%s) - start )) echo "v3 Stage 2d (lane2d) wall time: ${elapsed}s" - if [ "$elapsed" -gt 300 ]; then - echo "::error::v3 lane2d tests took ${elapsed}s (budget: 300s) — share bootstrap setup via OnceLock or collapse fine-grained tests" + if [ "$elapsed" -gt 420 ]; then + echo "::error::v3 lane2d tests took ${elapsed}s (budget: 420s) — share bootstrap setup via OnceLock or collapse fine-grained tests" exit 1 fi @@ -213,12 +213,14 @@ jobs: # `RUSTC_BOOTSTRAP=1` prefix is the documented narrow unlock for libtest flags # and does not enable unstable language features. - name: v3 tests (full suite, 2400s transitional budget) + id: v3_full_suite env: # Keep libtest lines plain for `scripts/check-test-timeout.sh` (Phase-0 # ratchet); workflow-level `CARGO_TERM_COLOR=always` would otherwise # inject ANSI into cargo's stream. CARGO_TERM_COLOR: never run: | + rm -f /tmp/v3-test-timings.log start=$(date +%s) RUSTC_BOOTSTRAP=1 cargo test -p v3-compiler -- -Z unstable-options --report-time 2>&1 | tee /tmp/v3-test-timings.log status=${PIPESTATUS[0]} @@ -241,12 +243,12 @@ jobs: # `src/v3/compiler/tests/HANDOFF_PHASE0.md` (TESTING.md § test layers, # `feedback_test_timeout_2s`). # - # **`if: always()`** — the ratchet report is still useful on red runs - # (full-suite failure, 2400s transitional-budget overflow). The log is captured via - # `tee` before the enforcement check, so partial timing data survives - # failure and the ratchet can still emit actionable slow-test output. + # Run only when the full-suite step actually executed (success or failure). + # If an earlier step (e.g. lane2d budget) aborts the job, `v3_full_suite` is + # skipped — do not parse a stale `/tmp/v3-test-timings.log` from a prior + # step or runner reuse (`if: always()` alone is insufficient). - name: v3 tests (per-test 2s ratchet) - if: always() + if: always() && (steps.v3_full_suite.outcome == 'success' || steps.v3_full_suite.outcome == 'failure') run: | if [ ! -s /tmp/v3-test-timings.log ]; then echo "::notice::no timing log captured or log is empty (full-suite step did not reach cargo test, or tee wrote nothing) — skipping per-test ratchet" @@ -332,7 +334,6 @@ jobs: - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1.16.0 with: - toolchain: "1.93.0" cache: false rustflags: "" diff --git a/ROADMAP.md b/ROADMAP.md index 7be8f9cbf4e..26e4a8e572f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -385,6 +385,7 @@ Per-row tagging is a scheduled follow-up sweep — **trigger:** post-merge of th - **`effects.dag` dual authority** — `dsl/std/effects.dag` (395 lines) vs `src/v3/std/effects.dag` (818 lines, diverged 2×). Dissolution: decide authoritative location, collapse the other; name a convergence lane. - **`container_template_algebra_rows` table duplicates type aliases** — `dsl/std/types.dag:133-146` is the string-keyed table (renamed from `container_to_algebra` in PR #651's narrow dissolution); `types.dag:211-213` declares `type List = FreeMonoid` etc. Dissolution: derive the table from the aliases when alias reflection lands. - **Parallel string-keyed authorities in types.dag + coercion.dag**: `kernel_primitives` (65-74), `container_arity` (79-91), `ordered_collections` (125-130), `TypeCheckpoint.dag_name: String` (38-44), `InhabitantDecl.algebra: String` (59-65). Header labels as transitional. Dissolution: derive from structural declarations. +- **`rust-toolchain.toml` vs `dsl/extdeps/rustup.dag::ci_pinned_toolchain`**: the pinned rustc channel string lives in both the repo-root TOML (rustup + `setup-rust-toolchain` after checkout) and `data ci_pinned_toolchain` in the extdep stub — P2 parallel-authority drift risk. PR #1789 removed a false “codegen” banner and documented alignment; it did not introduce the duplication (the TOML was already hand-maintained). Dissolution trigger: **`rust_toolchain_toml_codegen_from_rustup_dag`** — regenerate `rust-toolchain.toml` from `rustup.dag` (or fold `ci_pinned_toolchain` into a single file-backed authority) via bounded `gunbc-codegen` (or successor) so only one authored pin remains. Owner: T-Workflow-As-Data / extdeps hygiene as bandwidth allows. ### P3 — modeling gaps diff --git a/dsl/extdeps/github/actions.dag b/dsl/extdeps/github/actions.dag index 40878c6efba..8518f687cef 100644 --- a/dsl/extdeps/github/actions.dag +++ b/dsl/extdeps/github/actions.dag @@ -202,7 +202,7 @@ data checkout_action: ActionRef = ActionRef { } data setup_rust_action: ActionRef = ActionRef { - owner: "dtolnay", repo: "rust-toolchain", ref: "stable" + owner: "actions-rust-lang", repo: "setup-rust-toolchain", ref: "v1.16.0" } data cache_action: ActionRef = ActionRef { diff --git a/dsl/extdeps/rustup.dag b/dsl/extdeps/rustup.dag index f1a5b6bd77f..f5b0e506944 100644 --- a/dsl/extdeps/rustup.dag +++ b/dsl/extdeps/rustup.dag @@ -11,15 +11,16 @@ // What we actually depend on from rustup: // - cargo binary, installed by default with the stable toolchain // - rustc, installed by default -// - Pinned toolchain "1.93.0" in CI (.github/workflows/ci.yml) +// - Pinned toolchain "1.93.0" in repo-root rust-toolchain.toml (CI reads it) // // What we do NOT currently depend on: // - Custom components (clippy ships with stable, rustfmt ships with stable) // - Cross-compilation targets (gunbc only builds for the host) // - Multiple toolchains (no toolchain switching) // -// The CI workflow uses dtolnay/rust-toolchain@v1 to provision -// rustup. Local dev uses whatever rustup ships as stable. +// CI uses actions-rust-lang/setup-rust-toolchain (see setup_rust_action in +// extdeps/github/actions.dag); channel comes from rust-toolchain.toml. Local dev +// usually tracks stable unless overridden by that file. // // Future: // When we model toolchain switching, channels, components, or diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7828a71dec7..15d812021a8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,6 @@ -# Generated by gunbc-codegen. Do not edit manually. -# Source of truth: dsl/config/toolchain.dag +# Channel pin for local rustup and GitHub Actions (`setup-rust-toolchain` reads +# this file after checkout when no explicit `toolchain:` input is passed). +# Keep `channel` aligned with `dsl/extdeps/rustup.dag` `ci_pinned_toolchain`. [toolchain] channel = "1.93.0" components = ["clippy", "rustfmt"] diff --git a/scripts/check-rust-toolchain-pin-alignment.sh b/scripts/check-rust-toolchain-pin-alignment.sh new file mode 100755 index 00000000000..2216e831bc0 --- /dev/null +++ b/scripts/check-rust-toolchain-pin-alignment.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Fail closed if rust-toolchain.toml [toolchain].channel drifts from +# dsl/extdeps/rustup.dag `ci_pinned_toolchain` while both remain hand-authored +# (ROADMAP P2 / rust_toolchain_toml_codegen_from_rustup_dag). +set -euo pipefail +repo_root=$(cd "$(dirname "$0")/.." && pwd) +cd "$repo_root" + +toml_channel=$( + grep -E '^[[:space:]]*channel[[:space:]]*=' rust-toolchain.toml | + head -1 | + sed -E 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/' +) +dag_channel=$( + grep -E '^data ci_pinned_toolchain' dsl/extdeps/rustup.dag | + head -1 | + sed -E 's/^data ci_pinned_toolchain:[^=]*=[[:space:]]*"([^"]+)".*/\1/' +) + +if [ -z "${toml_channel}" ] || [ -z "${dag_channel}" ]; then + echo "::error::could not parse channel from rust-toolchain.toml and/or ci_pinned_toolchain from dsl/extdeps/rustup.dag" + exit 1 +fi + +if [ "${toml_channel}" != "${dag_channel}" ]; then + echo "::error::Rust toolchain pin mismatch: rust-toolchain.toml channel='${toml_channel}' vs dsl/extdeps/rustup.dag ci_pinned_toolchain='${dag_channel}'. Align both until rust_toolchain_toml_codegen_from_rustup_dag dissolves the duplicate (ROADMAP.md P2)." + exit 1 +fi + +echo "Rust toolchain pin alignment OK (${toml_channel})." diff --git a/src/v3/compiler/tests/integration.rs b/src/v3/compiler/tests/integration.rs index 14301b9a78d..0ad20b1ea43 100644 --- a/src/v3/compiler/tests/integration.rs +++ b/src/v3/compiler/tests/integration.rs @@ -205,6 +205,7 @@ mod t_demo_fixture_test { use std::fs; use std::path::PathBuf; + use std::sync::OnceLock; use v3_compiler::compile_to_dag; use v3_compiler::dag::Dag; @@ -216,6 +217,9 @@ mod t_demo_fixture_test { /// Byte-sync with `t_demo_structural_cost_obligation_gate.source` in `t_demo_fixtures.dag`. const T_DEMO_STRUCTURAL_COST_OBLIGATION_CLAIM_SOURCE: &str = "fn pair_score(xs: List) -> Int = fold(xs, 0, |outer, x| outer + fold(xs, 0, |inner, y| inner + x + y))\nlet complexity_demo_out: Int = pair_score(cons(1, singleton(2)))\n"; + // Process-wide cache: shared read-only `Dag`; tests that mutate or need isolation must compile locally. + static T_DEMO_FIXTURE_DAG: OnceLock = OnceLock::new(); + fn fixture_source() -> String { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../../..") @@ -227,11 +231,16 @@ mod t_demo_fixture_test { compile_to_dag(source, FIXTURE).expect("T-Demo fixture skeleton compiles") } + fn shared_t_demo_fixture_dag() -> &'static Dag { + T_DEMO_FIXTURE_DAG.get_or_init(|| { + let source = fixture_source(); + compile_fixture(&source) + }) + } + #[test] fn t_demo_fixture_skeleton_compiles() { - let source = fixture_source(); - let dag = compile_fixture(&source); - + let dag = shared_t_demo_fixture_dag(); assert!( dag.diagnostics().is_empty(), "T-Demo fixture skeleton should compile without diagnostics: {:?}", @@ -239,27 +248,30 @@ mod t_demo_fixture_test { ); } + fn assert_t_demo_canonical_suite_passes(dag: &Dag, suite_name: &str) { + let results = TestRunner::new(dag).run_suite(suite_name); + assert!( + !results.is_empty(), + "T-Demo suite `{suite_name}` should contain Day-1 Compiles claims" + ); + assert!( + results + .iter() + .all(|result| result.result == ClaimResult::Pass), + "T-Demo suite `{suite_name}` should pass Day-1 Compiles claims, got {results:?}" + ); + } + #[test] - fn t_demo_canonical_suites_are_runner_visible() { - let source = fixture_source(); - let dag = compile_fixture(&source); + fn t_demo_fixture_compiler_nerd_canonical_suite_is_runner_visible() { + let dag = shared_t_demo_fixture_dag(); + assert_t_demo_canonical_suite_passes(dag, "fixture_compiler_nerd_canonical"); + } - for suite_name in [ - "fixture_compiler_nerd_canonical", - "fixture_integration_canonical", - ] { - let results = TestRunner::new(&dag).run_suite(suite_name); - assert!( - !results.is_empty(), - "T-Demo suite `{suite_name}` should contain Day-1 Compiles claims" - ); - assert!( - results - .iter() - .all(|result| result.result == ClaimResult::Pass), - "T-Demo suite `{suite_name}` should pass Day-1 Compiles claims, got {results:?}" - ); - } + #[test] + fn t_demo_fixture_integration_canonical_suite_is_runner_visible() { + let dag = shared_t_demo_fixture_dag(); + assert_t_demo_canonical_suite_passes(dag, "fixture_integration_canonical"); } /// ROADMAP T-Demo / PR #764: `FailsWithDiagnostic.detail_contains` must pin the **sum @@ -283,9 +295,8 @@ mod t_demo_fixture_test { #[test] fn t_demo_impossible_bug_suite_r1_passes() { - let source = fixture_source(); - let dag = compile_fixture(&source); - let results = TestRunner::new(&dag).run_suite("impossible_bug_class_suite_r1"); + let dag = shared_t_demo_fixture_dag(); + let results = TestRunner::new(dag).run_suite("impossible_bug_class_suite_r1"); assert_eq!(results.len(), 2); assert!( results @@ -301,9 +312,8 @@ mod t_demo_fixture_test { /// surface is user-extensible (THESIS §"User-defined dimensions"). #[test] fn t_demo_user_authored_lens_rejects_violating_program_passes() { - let source = fixture_source(); - let dag = compile_fixture(&source); - let results = TestRunner::new(&dag) + let dag = shared_t_demo_fixture_dag(); + let results = TestRunner::new(dag) .run_suite("demo_user_authored_lens_rejects_violating_program_suite"); assert_eq!(results.len(), 1); assert!(