diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48d2b409009..042c4dba3f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,8 +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: "" @@ -80,6 +78,9 @@ jobs: - name: Fabrication sentinel ratchet (P0-C) run: bash scripts/check-fabrication-sentinels.sh + - name: Rust toolchain single-authority check (P2) + run: bash scripts/check-rust-toolchain-single-authority.sh + - name: Release-doc authority check (P2 single-authority discipline) # Forbidden-string consumer for release-control docs. Authority: # docs/r2-structure.md §"Release-doc authority discipline". @@ -108,8 +109,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,9 +144,8 @@ 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 + # Channel + components: repo-root `rust-toolchain.toml` only (setup-rust-toolchain + # installs the file first; do not duplicate `components:` here — P2 single-authority). cache: false rustflags: "" @@ -191,15 +189,17 @@ jobs: # The timer wraps the full `cargo test` process, so **cold** runs (cache # 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) + # `tests/common/budgeted.rs` (DEFAULT_BUDGET_MS). Cold-cache runs have + # observed ~313s wall for this filter alone (compile-heavy v2 in the + # integration binary graph); budget stays coarse vs. per-test ratchet below. + - 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 @@ -332,7 +332,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..3c9c8371744 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -429,7 +429,7 @@ Tracked debt not dispatched with the 2026-04-21 lane wave (Lane 1e P3.0, SG-2c-4 - **Lane E-I — port `SubValueRelation` + inductive fields + cost algebra (M)**: port Tier 1 (structural) + Tier 2 (lattice) + Tier 3 (`CostBound` + master theorem). Requires E-T, E-C. Pre-flight: verify v3 lowers `CostBound`'s self-referential-through-`List` shape. Acceptance: carrier types ported with round-trip tests; no lens row moves on E-I alone because consumer-visible facts still wait for E-P producer/consumer wiring. - **Lane E-P — per-call descent-evidence provenance (M)**: first P-c side-table producer slice has landed as `v3_compiler::dag::per_call_descent_evidence` (recursive self-call / arithmetic descent; unproven edges fail closed; `TransformNode` stays unwidened). v2 stores this class of fact on `ExprCall.descent_evidence` (`src/v2/00_core.dag:199`); v3 derives it off-substrate for now. Requires E-T, E-C, E-I. Remaining acceptance: cost/complexity lenses consume the side table, plus a v2-oracle-vs-v3 cementing test against `expr_call_descent_evidence`; carrier parity for `cost.dag` / `complexity.dag` on the non-method-dispatch slice closes there. - **Lane E-M — `MethodSemantics` port-or-subsume (S–M)**: **closed via M-b structural subsumption.** Transitive facts (`CollectionSizeEffect` / `CostShape` / `AlgebraFieldTemplate`) remain algebra-declaration facts; v3 method-like calls dispatch through `TransformTarget::{Callable, FieldProject, Operator}` and typed declaration/effect metadata. No `src/v3/std/method_semantics.dag`, no Rust mirror, and no `ExprMethodCall` attachment point. -- **CI ratchet architecture — PARTIAL (T-Receipts W1 second bundle)**: surfaced during the 2026-04-21 reflective analysis (`53b3110..ae8825a` range). Commits `37cd6128`, `4898983e`, `f84ed355` hardened the ratchet against CI-log instability, but `2d8396df` widened the exemption list. `scripts/slow-test-exemptions.txt` currently has **39** active non-comment exemptions; `scripts/check-test-timeout.sh` meta-ratchet default `TEST_TIMEOUT_MAX_EXEMPTIONS` matches that count. **Landed partial:** classification audit [docs/debt/ci-ratchet-exemption-audit-2026-04-24.md](docs/debt/ci-ratchet-exemption-audit-2026-04-24.md) (2026-04-24 baseline) plus follow-up routing audit [docs/debt/ci-ratchet-exemption-audit-2026-05-02.md](docs/debt/ci-ratchet-exemption-audit-2026-05-02.md) (bounded scope — not a completed Phase 1 per-test timing pass). **Still open:** fresh CI-shaped per-test timing audit, stale exemption deletion if any entries measure under 2s, and per-exempt duration budgets once fresh timings exist. Dispatch brief: `docs/briefs/ci-ratchet-architecture-audit.md`. +- **CI ratchet architecture — PARTIAL (T-Receipts W1 second bundle)**: surfaced during the 2026-04-21 reflective analysis (`53b3110..ae8825a` range). Commits `37cd6128`, `4898983e`, `f84ed355` hardened the ratchet against CI-log instability, but `2d8396df` widened the exemption list. `scripts/slow-test-exemptions.txt` currently has **40** active non-comment exemptions; `scripts/check-test-timeout.sh` meta-ratchet default `TEST_TIMEOUT_MAX_EXEMPTIONS` matches that count. **Landed partial:** classification audit [docs/debt/ci-ratchet-exemption-audit-2026-04-24.md](docs/debt/ci-ratchet-exemption-audit-2026-04-24.md) (2026-04-24 baseline) plus follow-up routing audit [docs/debt/ci-ratchet-exemption-audit-2026-05-02.md](docs/debt/ci-ratchet-exemption-audit-2026-05-02.md) (bounded scope — not a completed Phase 1 per-test timing pass). **Still open:** fresh CI-shaped per-test timing audit, stale exemption deletion if any entries measure under 2s, and per-exempt duration budgets once fresh timings exist. Dispatch brief: `docs/briefs/ci-ratchet-architecture-audit.md`. - **`IntegrationRsScan` / `integration_rs_active_line_contains` in `src/v3/compiler/tests/integration/common/mod.rs` is a latent source of byte-constant workarounds in downstream tests** (T-Receipts, surfaced by T-Demo #686). The scanner is "deliberately narrow" and does not model Rust character literals, so any test author touching a file in its scan path cannot write `b'\\'` / `b'"'` directly and must substitute numeric byte constants (e.g., `92` / `34`) instead. Historical evidence: #686's text-slicing helper introduced `DAG_ESCAPE_BYTE` / `DAG_QUOTE_BYTE` constants for exactly this reason; #705 deleted the helper when `TestRunner::run_suite` obsoleted it, and the constants went with it — so there is **no surviving in-tree instance of the workaround today**. The scanner constraint itself remains, which means the next test author landing in the scan path will recreate the pattern. Dissolution: either widen the scanner to model Rust char literals, or replace its scan with a structural reader that doesn't need to exclude char-literal syntax. Small scope; cosmetic for now, but the constraint is a workaround attractor. Owner: unassigned; P2 Boundary Discipline / T-Receipts bundle. - **Stale-receipt sweep in `docs/briefs/` — PARTIAL (T-Receipts W1).** The cited `DeclarationLookup` cleanup brief now carries a historical-receipt banner stating the lane is closed by SG-4b-1-fix and that `DeclarationLookup` / `find_declaration` are gone from `src/v3/lenses/variant_payload.dag`; its stale INVARIANTS refs were also updated to stable anchors. Remaining broad `docs/briefs/` sweep stays open for later post-wave stale claims. **Include:** `docs/design-pure-bootstrap.md`'s hand-maintained count ("78 hand-maintained .rs files") was stale vs. the SG-0 census — **CLOSED in this R1 PR**: all five occurrences (lines 7, 49, 82, 105, 242 of the design doc) now point at the live census, reframed as deltas against the illustrative 78-file baseline, or scoped to the non-test + TESTING residual framing. No frozen absolute count remains. - **Compiler–`std/` consolidation program — specific migrations**: end-state defined in [docs/thesis/compiler-std-consolidation.md](docs/thesis/compiler-std-consolidation.md). Each of the migrations below is a standalone lane; together they collapse the compiler-specific type surface toward the positive definition (pipeline + regen + lens-specific return-type carriers + accessor). **Ratchet:** count of `type` declarations in `src/v3/compiler/*.dag` AND `src/v3/lenses/*.dag` that are NOT in the positive-def set AND NOT exempted → 0. Positive-def: pipeline/regen types + lens-API return carriers (`Origin`, `UnusedParameter`, `VariantPayloadShapeLookup` 3-variant, `TemplateArgumentBinding` semantic carrier, etc.). Exempted: `parse_tables.dag` (7 types, pending SG-2c-proper per-row classification). In-ratchet: **workaround-shaped infer-helper coproducts with named dissolution triggers (`TemplateArgumentsMatch`, `TemplateArgumentCursor`, `NormalizedInstantiationArgs`)** — Lookup-pattern carriers on lens surfaces import `v3.std.lookup::Lookup` (`complexity`, `cost`, `infer_helpers`). Primary count: 3. See thesis doc table for per-file disposition. diff --git a/dsl/extdeps/github/actions.dag b/dsl/extdeps/github/actions.dag index 40878c6efba..e3bfbad77fc 100644 --- a/dsl/extdeps/github/actions.dag +++ b/dsl/extdeps/github/actions.dag @@ -202,7 +202,9 @@ 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..8b2996d42da 100644 --- a/dsl/extdeps/rustup.dag +++ b/dsl/extdeps/rustup.dag @@ -11,15 +11,18 @@ // 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 channel + components in repo-root rust-toolchain.toml +// (read by CI `actions-rust-lang/setup-rust-toolchain` after checkout; no +// duplicate `components:` in `.github/workflows/ci.yml`) // // What we do NOT currently depend on: -// - Custom components (clippy ships with stable, rustfmt ships with stable) +// - Rustup components beyond those listed in rust-toolchain.toml (today +// clippy + rustfmt; no llvm-tools-preview, rust-src, miri, etc.) // - 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 extdeps/github/actions.dag +// `setup_rust_action`). Local dev follows rust-toolchain.toml via rustup. // // Future: // When we model toolchain switching, channels, components, or @@ -44,9 +47,5 @@ data rustup_install_url: NonEmptyStr = "https://rustup.rs" // Default channel we expect when "rustup default" is queried. data default_channel: RustChannel = Stable -// The pinned toolchain version used in CI. Local dev usually tracks -// stable; CI pins for reproducibility. -data ci_pinned_toolchain: NonEmptyStr = "1.93.0" - // Rustup docs URL for reference. data rustup_docs: NonEmptyStr = "https://rust-lang.github.io/rustup/" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7828a71dec7..617ee205a0c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,7 @@ -# Generated by gunbc-codegen. Do not edit manually. -# Source of truth: dsl/config/toolchain.dag +# Canonical Rust toolchain (channel + components) for local rustup and for +# `actions-rust-lang/setup-rust-toolchain` after checkout. CI must not pass a +# parallel `components:` override — this file is the sole authority (P2). +# DSL notes: `dsl/extdeps/rustup.dag` (no second semver pin constant). [toolchain] channel = "1.93.0" components = ["clippy", "rustfmt"] diff --git a/scripts/check-rust-toolchain-single-authority.sh b/scripts/check-rust-toolchain-single-authority.sh new file mode 100755 index 00000000000..76e1a20644c --- /dev/null +++ b/scripts/check-rust-toolchain-single-authority.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# P2 single-authority: the pinned Rust channel + components live only in repo-root +# rust-toolchain.toml (read by rustup locally and by setup-rust-toolchain in CI). +# dsl/extdeps/rustup.dag documents rustup; it must not reintroduce a parallel +# numeric channel constant. GitHub workflows must not reintroduce parallel +# `toolchain:` / `components:` inputs on setup-rust-toolchain (same authority). +# +# Usage: ./scripts/check-rust-toolchain-single-authority.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$ROOT" + +rustup_dag="dsl/extdeps/rustup.dag" +toolchain_toml="rust-toolchain.toml" + +if [[ ! -f "$rustup_dag" ]] || [[ ! -f "$toolchain_toml" ]]; then + echo "error: missing $rustup_dag or $toolchain_toml" >&2 + exit 1 +fi + +if grep -n 'ci_pinned_toolchain' "$rustup_dag" 2>/dev/null; then + echo "error: ci_pinned_toolchain must not exist in $rustup_dag (sole pin: $toolchain_toml)" >&2 + exit 1 +fi + +if grep -nE '^\s*data\s+\w+\s*:\s*NonEmptyStr\s*=\s*"[0-9]+\.[0-9]+\.[0-9]+"' "$rustup_dag" 2>/dev/null; then + echo "error: semver-like NonEmptyStr data in $rustup_dag — channel pin belongs only in $toolchain_toml" >&2 + exit 1 +fi + +if ! grep -qE '^channel\s*=' "$toolchain_toml"; then + echo "error: $toolchain_toml missing channel = entry" >&2 + exit 1 +fi + +# Fail if any workflow reintroduces `toolchain:` / `components:` mapping keys +# (rust-toolchain.toml is sole authority). Keys are lines with leading +# whitespace then the token — excludes full-line `#` comments. +workflow_dir=".github/workflows" +if [[ -d "$workflow_dir" ]]; then + while IFS= read -r -d '' wf; do + if grep -nE '^[[:space:]]+toolchain:[[:space:]]' "$wf" 2>/dev/null; then + echo "error: explicit toolchain: pin in $wf — use rust-toolchain.toml only" >&2 + exit 1 + fi + if grep -nE '^[[:space:]]+components:[[:space:]]' "$wf" 2>/dev/null; then + echo "error: explicit components: list in $wf — use rust-toolchain.toml only" >&2 + exit 1 + fi + done < <(find "$workflow_dir" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) -print0) +fi + +echo "check-rust-toolchain-single-authority: ok" diff --git a/scripts/check-test-timeout.sh b/scripts/check-test-timeout.sh index 928c4976c82..e7ee88c67cd 100755 --- a/scripts/check-test-timeout.sh +++ b/scripts/check-test-timeout.sh @@ -46,8 +46,8 @@ # (default scripts/slow-test-exemptions.txt). # TEST_TIMEOUT_MAX_EXEMPTIONS # Ratchet floor for active exemption entries -# (default 39, captured 2026-04-25). Lower this -# value in the same PR that removes exemptions. +# (default 40; bump in the same PR that adds lines). +# Lower this value in the same PR that removes exemptions. set -euo pipefail @@ -55,7 +55,7 @@ log_file_arg=${1:-} budget_ms=${2:-${TEST_TIMEOUT_MS:-2000}} pkg=${TEST_TIMEOUT_PACKAGE:-v3-compiler} exempt_file=${TEST_TIMEOUT_EXEMPT:-scripts/slow-test-exemptions.txt} -max_exemptions=${TEST_TIMEOUT_MAX_EXEMPTIONS:-39} +max_exemptions=${TEST_TIMEOUT_MAX_EXEMPTIONS:-40} script_dir=$(cd "$(dirname "$0")" && pwd) repo_root=$(cd "$script_dir/.." && pwd) diff --git a/scripts/slow-test-exemptions.txt b/scripts/slow-test-exemptions.txt index 8bf6d06e206..0ab3a95fd10 100644 --- a/scripts/slow-test-exemptions.txt +++ b/scripts/slow-test-exemptions.txt @@ -77,4 +77,6 @@ sg2_parse_authority_test::parse_surface_dag_compiles_cleanly_for_regen_parse # sg2c1_parse_tables_authority_test::parse_tables_generated_module_matches_checked_in_snapshot # SG-2c-1 grammar-tables prototype snapshot: in-process authority compile (parse_tables.dag + tokenize.dag) + rustfmt stdout; same paydown class as SG-1 tokenize / SG-2 parse snapshot gates. sg6_hand_authored_census_test::sg6_regen_lens_cli_smoke_regenerates_named_entry_without_drift # SG-6 regen CLI smoke intentionally shells out to regen path; CLI smoke cost is tracked in SG-6, not TM-0. +t_demo_fixture_test::t_demo_canonical_suites_are_runner_visible # ROADMAP T-Demo / R1C-F: `compile_to_dag` on `t_demo_fixtures.dag` plus two `TestRunner::run_suite` passes; observed 2573ms on PR #1796 CI (cold ubuntu-latest / neighbor parallelism). Paydown: shared fixture `Dag` warming across T-Demo integration tests (TESTING.md § test layers / shared-setup), not TM-0. + thesis_validation_test::kf_1_structural_list_operation_ordering_holds # KF-1 thesis validation remains compile-heavy on CI runners; TESTING.md test-shape paydown owns future decomposition, not TM-0.