From 81ae957e285811d3e4ccefd6df4fc5bb8d3c165c Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 13:51:33 -0400 Subject: [PATCH 01/17] WIP: quick-ferret-413 --- .github/workflows/ci.yml | 4 ---- dsl/extdeps/github/actions.dag | 2 +- dsl/extdeps/rustup.dag | 12 ++++++------ rust-toolchain.toml | 4 ++-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6474e40e7d5..4b94346b0dc 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: "" @@ -124,7 +123,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: "" @@ -171,7 +169,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 @@ -367,7 +364,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/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..313a2e2adac 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 channel in rust-toolchain.toml (must match ci_pinned_toolchain below) // // What we do NOT currently depend on: -// - Custom components (clippy ships with stable, rustfmt ships with stable) +// - Extra rustup components beyond rust-toolchain.toml (rustfmt + clippy today) // - 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 .github/workflows/ci.yml), which +// reads rust-toolchain.toml. Local dev uses rust-toolchain.toml when present, else +// whatever rustup ships as stable. // // Future: // When we model toolchain switching, channels, components, or @@ -44,8 +45,7 @@ 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. +// Pinned channel string — must match rust-toolchain.toml [toolchain].channel (CI + local). data ci_pinned_toolchain: NonEmptyStr = "1.93.0" // Rustup docs URL for reference. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7828a71dec7..97c694ac9d2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ -# Generated by gunbc-codegen. Do not edit manually. -# Source of truth: dsl/config/toolchain.dag +# Rustup install authority for this repo. Keep [toolchain].channel aligned with +# dsl/extdeps/rustup.dag `ci_pinned_toolchain`. [toolchain] channel = "1.93.0" components = ["clippy", "rustfmt"] From dd3d614953fb1c06f4804511cf9e473485a2bd40 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 17:59:17 +0000 Subject: [PATCH 02/17] ci+docs: toolchain review follow-up; fix v2 clippy on 1.93 - Document in ci.yml that setup-rust-toolchain reads repo-root rust-toolchain.toml when toolchain is omitted, and that no job uses a non-default working-directory. - Clarify rust-toolchain.toml is hand-maintained (nothing regenerates it). - Align design-fixed-point-ratchet CI example with actions-rust-lang setup. - Satisfy clippy 1.93 in v2-compiler-tests (manual_contains, enum_variant_names). Co-authored-by: Cursor --- .github/workflows/ci.yml | 4 +++ docs/design-fixed-point-ratchet.md | 5 ++-- rust-toolchain.toml | 3 ++- ...ethod_template_projection_consumability.rs | 4 +-- src/v2/tests/src/pipeline.rs | 27 +++++++++++-------- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b94346b0dc..9c64cc68438 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,10 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: true +# Rust: each `setup-rust-toolchain` step omits `toolchain:` so the action reads +# `rust-toolchain.toml` at the repo root after `actions/checkout`. No job uses a +# non-default `working-directory` for Rust setup; if one did, you would need an +# explicit `toolchain:` input or a copy of the file inside that directory. env: CARGO_TERM_COLOR: always RUSTFLAGS: -D warnings diff --git a/docs/design-fixed-point-ratchet.md b/docs/design-fixed-point-ratchet.md index ead7200bd6d..70c6f5dde79 100644 --- a/docs/design-fixed-point-ratchet.md +++ b/docs/design-fixed-point-ratchet.md @@ -97,9 +97,10 @@ self_host: needs: [ci, v3] # runs after basic build + v3 tests pass steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions-rust-lang/setup-rust-toolchain@v1.16.0 with: - toolchain: "1.93.0" + cache: false + rustflags: "" - name: Cache Cargo (self_host) uses: actions/cache@v4 with: diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 97c694ac9d2..d4a34c9e43b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,5 @@ -# Rustup install authority for this repo. Keep [toolchain].channel aligned with +# Rustup install authority for this repo. Hand-maintained (no regen step +# overwrites this file). Keep [toolchain].channel aligned with # dsl/extdeps/rustup.dag `ci_pinned_toolchain`. [toolchain] channel = "1.93.0" diff --git a/src/v2/tests/src/pb_method_template_projection_consumability.rs b/src/v2/tests/src/pb_method_template_projection_consumability.rs index a3723e8e1b2..cf831582587 100644 --- a/src/v2/tests/src/pb_method_template_projection_consumability.rs +++ b/src/v2/tests/src/pb_method_template_projection_consumability.rs @@ -122,9 +122,7 @@ fn rust_count_template() -> Map { rust_method_template_emit } .to_string_lossy() .to_string(); assert!( - loaded_paths - .iter() - .any(|path| *path == expected_generated_path.as_str()), + loaded_paths.contains(&expected_generated_path.as_str()), "expected the ephemeral generated module to load at exactly \ {expected_generated_path}; got: {loaded_paths:?}" ); diff --git a/src/v2/tests/src/pipeline.rs b/src/v2/tests/src/pipeline.rs index dff82662719..82b423cffc8 100644 --- a/src/v2/tests/src/pipeline.rs +++ b/src/v2/tests/src/pipeline.rs @@ -7549,9 +7549,10 @@ fn anthropic_messages_200_residual_fields_round_trip_representative_wire() { } #[derive(serde::Deserialize)] - #[serde(tag = "type", rename_all = "snake_case")] + #[serde(tag = "type")] enum Citation { - CharLocation { + #[serde(rename = "char_location")] + Char { cited_text: String, document_index: i64, document_title: Option, @@ -7559,7 +7560,8 @@ fn anthropic_messages_200_residual_fields_round_trip_representative_wire() { start_char_index: i64, end_char_index: i64, }, - PageLocation { + #[serde(rename = "page_location")] + Page { cited_text: String, document_index: i64, document_title: Option, @@ -7567,7 +7569,8 @@ fn anthropic_messages_200_residual_fields_round_trip_representative_wire() { start_page_number: i64, end_page_number: i64, }, - ContentBlockLocation { + #[serde(rename = "content_block_location")] + ContentBlock { cited_text: String, document_index: i64, document_title: Option, @@ -7575,13 +7578,15 @@ fn anthropic_messages_200_residual_fields_round_trip_representative_wire() { start_block_index: i64, end_block_index: i64, }, - WebSearchResultLocation { + #[serde(rename = "web_search_result_location")] + WebSearchResult { cited_text: String, encrypted_index: String, title: Option, url: String, }, - SearchResultLocation { + #[serde(rename = "search_result_location")] + SearchResult { cited_text: String, source: String, title: Option, @@ -7659,7 +7664,7 @@ fn anthropic_messages_200_residual_fields_round_trip_representative_wire() { let body: Body = serde_json::from_value(wire).expect("representative Anthropic 200 wire"); let citations = body.content[0].citations.as_ref().unwrap(); match &citations[0] { - Citation::CharLocation { + Citation::Char { cited_text, document_index, document_title, @@ -7677,7 +7682,7 @@ fn anthropic_messages_200_residual_fields_round_trip_representative_wire() { _ => panic!("expected char_location citation"), } match &citations[1] { - Citation::PageLocation { + Citation::Page { cited_text, document_index, document_title, @@ -7695,7 +7700,7 @@ fn anthropic_messages_200_residual_fields_round_trip_representative_wire() { _ => panic!("expected page_location citation"), } match &citations[2] { - Citation::ContentBlockLocation { + Citation::ContentBlock { cited_text, document_index, document_title, @@ -7713,7 +7718,7 @@ fn anthropic_messages_200_residual_fields_round_trip_representative_wire() { _ => panic!("expected content_block_location citation"), } match &citations[3] { - Citation::WebSearchResultLocation { + Citation::WebSearchResult { cited_text, encrypted_index, title, @@ -7727,7 +7732,7 @@ fn anthropic_messages_200_residual_fields_round_trip_representative_wire() { _ => panic!("expected web_search_result_location citation"), } match &citations[4] { - Citation::SearchResultLocation { + Citation::SearchResult { cited_text, source, title, From c8530fd2aafa66b23de1202f135a99f50be79d58 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 14:31:12 -0400 Subject: [PATCH 03/17] WIP: quick-ferret-413 --- src/v3/compiler/tests/integration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v3/compiler/tests/integration.rs b/src/v3/compiler/tests/integration.rs index 568829db96d..dfc63286fcf 100644 --- a/src/v3/compiler/tests/integration.rs +++ b/src/v3/compiler/tests/integration.rs @@ -215,6 +215,8 @@ mod t_demo_fixture_test { const FIXTURE: &str = "src/v3/compiler/tests/t_demo/t_demo_fixtures.dag"; + static T_DEMO_FIXTURE_DAG: OnceLock = OnceLock::new(); + /// 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"; @@ -229,8 +231,6 @@ mod t_demo_fixture_test { cached_compile_to_dag(source, FIXTURE) } - static T_DEMO_FIXTURE_DAG: OnceLock = OnceLock::new(); - fn cached_t_demo_fixture_dag() -> &'static Dag { T_DEMO_FIXTURE_DAG.get_or_init(|| compile_fixture(&fixture_source())) } From 2e33a7651757b454e3bcfab6f5d1dac76e15e48c Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 14:37:00 -0400 Subject: [PATCH 04/17] WIP: quick-ferret-413 --- scripts/slow-test-exemptions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/slow-test-exemptions.txt b/scripts/slow-test-exemptions.txt index 532492b92af..482d032a3e0 100644 --- a/scripts/slow-test-exemptions.txt +++ b/scripts/slow-test-exemptions.txt @@ -78,6 +78,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 (Lane M): cold `compile_to_dag` on full `t_demo_fixtures.dag` + two `TestRunner::run_suite` passes; ~2.6–3.3s wall on isolated/filtered runs (above 2s Phase-0 ratchet). `compile_fixture` uses `cached_compile_to_dag` to dedupe with `t_demo_fixture_skeleton_compiles`; paydown: slimmer fixture / shared runner warming in T-Demo lane. +t_demo_fixture_test::t_demo_canonical_suites_are_runner_visible # ROADMAP T-Demo (Lane M): cold `compile_to_dag` on full `t_demo_fixtures.dag` + two `TestRunner::run_suite` passes; ~2.6–3.3s wall on isolated/filtered runs (above 2s Phase-0 ratchet). `compile_fixture` uses `cached_compile_to_dag`; module `OnceLock` still shares one `Dag` across sibling tests. Dissolution: delete this line and lower `TEST_TIMEOUT_MAX_EXEMPTIONS` in the same commit once `RUSTC_BOOTSTRAP=1 cargo test -p v3-compiler t_demo_canonical_suites_are_runner_visible -- -Z unstable-options --report-time` shows <=2000ms wall on cold ubuntu-latest after fixture slimming or shared-runner warming (TESTING.md). 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. From aed574b9fb582dd5f2d8ecb0a49d91c62f098272 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 18:44:30 +0000 Subject: [PATCH 05/17] fix(ci): single authority for pinned Rust channel (P2) Remove duplicate `ci_pinned_toolchain` from dsl/extdeps/rustup.dag; the only channel literal is rust-toolchain.toml. Add a fail-closed CI script that rejects reintroducing the old data row, and wire it into the ci job. Addresses api-review REQUEST_CHANGES on PR #1794 (comment-only coupling between TOML and .dag). Co-authored-by: Cursor --- .github/workflows/ci.yml | 5 +++ dsl/extdeps/rustup.dag | 6 ++-- rust-toolchain.toml | 6 ++-- .../check-rust-toolchain-single-authority.sh | 34 +++++++++++++++++++ 4 files changed, 44 insertions(+), 7 deletions(-) create mode 100755 scripts/check-rust-toolchain-single-authority.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c64cc68438..540f3dd7798 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,6 +124,11 @@ jobs: # silently neutering the consumer. run: bash scripts/test-check-manager-brief-authority.sh + - name: Rust toolchain single-authority check (P2) + # Pinned channel must not be duplicated in dsl/extdeps/rustup.dag; + # authority is rust-toolchain.toml only (PR #1794). + run: bash scripts/check-rust-toolchain-single-authority.sh + - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1.16.0 with: diff --git a/dsl/extdeps/rustup.dag b/dsl/extdeps/rustup.dag index 313a2e2adac..4309513c732 100644 --- a/dsl/extdeps/rustup.dag +++ b/dsl/extdeps/rustup.dag @@ -11,7 +11,8 @@ // What we actually depend on from rustup: // - cargo binary, installed by default with the stable toolchain // - rustc, installed by default -// - Pinned toolchain channel in rust-toolchain.toml (must match ci_pinned_toolchain below) +// - Pinned toolchain channel: **single authority** is `rust-toolchain.toml` +// `[toolchain].channel` only (this stub does not duplicate that string). // // What we do NOT currently depend on: // - Extra rustup components beyond rust-toolchain.toml (rustfmt + clippy today) @@ -45,8 +46,5 @@ data rustup_install_url: NonEmptyStr = "https://rustup.rs" // Default channel we expect when "rustup default" is queried. data default_channel: RustChannel = Stable -// Pinned channel string — must match rust-toolchain.toml [toolchain].channel (CI + local). -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 d4a34c9e43b..ba5604ab1c4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ -# Rustup install authority for this repo. Hand-maintained (no regen step -# overwrites this file). Keep [toolchain].channel aligned with -# dsl/extdeps/rustup.dag `ci_pinned_toolchain`. +# Sole in-repo rustup channel authority (CI + local). Hand-maintained (no +# regen step overwrites this file). `dsl/extdeps/rustup.dag` documents rustup +# installer behavior only — it must not introduce a second pinned channel literal. [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..c9eb6b9cbab --- /dev/null +++ b/scripts/check-rust-toolchain-single-authority.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# +# P2 single-authority: the pinned rustc channel string must not exist in two +# independent editable places. Authoritative channel is `rust-toolchain.toml` +# `[toolchain].channel` only. `dsl/extdeps/rustup.dag` documents rustup +# installer behavior and must not reintroduce a parallel `ci_pinned_toolchain` +# data declaration (fail closed — see PR #1794 / INVARIANTS P2). +# +# Dissolution: delete this script and its CI step if extdeps gains a generated +# conformance link to rust-toolchain.toml instead of a negative guard. + +set -euo pipefail + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/.." && pwd) +rustup_dag="$repo_root/dsl/extdeps/rustup.dag" + +if [ ! -r "$rustup_dag" ]; then + echo "::error::missing $rustup_dag" + exit 2 +fi + +if grep -Eq '^[[:space:]]*data[[:space:]]+ci_pinned_toolchain' "$rustup_dag"; then + echo "::error::dsl/extdeps/rustup.dag declares ci_pinned_toolchain — duplicate channel authority. Use rust-toolchain.toml only." + exit 1 +fi + +toolchain_toml="$repo_root/rust-toolchain.toml" +if ! grep -Eq '^[[:space:]]*channel[[:space:]]*=[[:space:]]*"' "$toolchain_toml"; then + echo "::error::rust-toolchain.toml must contain a quoted [toolchain].channel line" + exit 1 +fi + +echo "Rust toolchain single-authority check OK (no ci_pinned_toolchain in rustup.dag; rust-toolchain.toml present)." From 9ae0b91669f827d331aedc06a48e8a6d072b3db7 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 18:49:42 +0000 Subject: [PATCH 06/17] ci(v3): prebuild integration harness before lane2d wall gate The Stage 2d step timed wall-clock around `cargo test ... lane2_stage_2d`, which on cold ubuntu-latest spent ~3m50s linking the full integration binary before running ~1s of filtered tests (322s > 300s budget; PR #1794). Add an untimed `cargo test -p v3-compiler --test integration --no-run` step so the 300s ratchet measures lane2d work, not cold compile inflation. Co-authored-by: Cursor --- .github/workflows/ci.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 540f3dd7798..7018e151445 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -220,13 +220,17 @@ jobs: # Wall-clock gate on lane2 Stage 2d symbolic-cost tests only (ζ / #537). # Post-#546 these live in the consolidated `integration` test binary; the # `lane2_stage_2d_symbolic_cost_test::` filter keeps the ratchet narrow. - # 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 + # The timer wraps the full `cargo test` process. Prebuild the integration + # harness (`--no-run`) so this gate measures lane2d work — not a cold link + # of the whole `integration` binary (observed ~322s over a 300s budget on + # ubuntu-latest; PR #1794). **Cold** runs (cache miss) can still spend minutes; + # keep headroom beyond a tight 120s; per-test discipline lives in # `tests/common/budgeted.rs` (DEFAULT_BUDGET_MS). - # Tracked headroom: ratchet this ceiling back toward 300s once cold - # lane2d CI runs regularly complete under 300s after fixture compile - # amortization lands for the remaining Stage 2d cases. + # Tracked headroom: ratchet this ceiling back toward 300s once cold lane2d + # CI runs regularly complete under 300s after fixture compile amortization. + - name: Prebuild v3 integration test binary (lane2d wall-clock denominator) + run: cargo test -p v3-compiler --test integration --no-run + - name: v3 tests (Stage 2d integration module, 360s cold-compile-safe budget) run: | start=$(date +%s) From 038bdf07ca9f714c14fe8d71cd1ba8eb949b41ec Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 18:34:56 -0400 Subject: [PATCH 07/17] WIP: quick-ferret-413 --- .../check-rust-toolchain-single-authority.sh | 40 ++++++++++++++----- scripts/slow-test-exemptions.txt | 4 ++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/scripts/check-rust-toolchain-single-authority.sh b/scripts/check-rust-toolchain-single-authority.sh index c9eb6b9cbab..0738ddbeb22 100755 --- a/scripts/check-rust-toolchain-single-authority.sh +++ b/scripts/check-rust-toolchain-single-authority.sh @@ -1,34 +1,52 @@ #!/usr/bin/env bash # -# P2 single-authority: the pinned rustc channel string must not exist in two -# independent editable places. Authoritative channel is `rust-toolchain.toml` -# `[toolchain].channel` only. `dsl/extdeps/rustup.dag` documents rustup -# installer behavior and must not reintroduce a parallel `ci_pinned_toolchain` -# data declaration (fail closed — see PR #1794 / INVARIANTS P2). +# P2 single-authority: the pinned rustc channel string lives only in +# `rust-toolchain.toml` `[toolchain].channel`. `dsl/extdeps/rustup.dag` must not +# reintroduce that literal (any `data … = ""` or comment drift) nor the +# retired `ci_pinned_toolchain` symbol — see PR #1794 / INVARIANTS P2. # # Dissolution: delete this script and its CI step if extdeps gains a generated -# conformance link to rust-toolchain.toml instead of a negative guard. +# conformance link to rust-toolchain.toml instead of this guard. set -euo pipefail script_dir=$(cd "$(dirname "$0")" && pwd) repo_root=$(cd "$script_dir/.." && pwd) rustup_dag="$repo_root/dsl/extdeps/rustup.dag" +toolchain_toml="$repo_root/rust-toolchain.toml" if [ ! -r "$rustup_dag" ]; then echo "::error::missing $rustup_dag" exit 2 fi -if grep -Eq '^[[:space:]]*data[[:space:]]+ci_pinned_toolchain' "$rustup_dag"; then - echo "::error::dsl/extdeps/rustup.dag declares ci_pinned_toolchain — duplicate channel authority. Use rust-toolchain.toml only." - exit 1 +if [ ! -r "$toolchain_toml" ]; then + echo "::error::missing $toolchain_toml" + exit 2 fi -toolchain_toml="$repo_root/rust-toolchain.toml" if ! grep -Eq '^[[:space:]]*channel[[:space:]]*=[[:space:]]*"' "$toolchain_toml"; then echo "::error::rust-toolchain.toml must contain a quoted [toolchain].channel line" exit 1 fi -echo "Rust toolchain single-authority check OK (no ci_pinned_toolchain in rustup.dag; rust-toolchain.toml present)." +channel=$( + sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' "$toolchain_toml" | head -n1 +) +if [ -z "$channel" ]; then + echo "::error::could not parse [toolchain].channel from rust-toolchain.toml" + exit 1 +fi + +quoted_channel="\"${channel}\"" +if grep -Fq "$quoted_channel" "$rustup_dag"; then + echo "::error::dsl/extdeps/rustup.dag contains the pinned channel literal ${quoted_channel} — duplicate authority (keep the channel only in rust-toolchain.toml)." + exit 1 +fi + +if grep -Eq '^[[:space:]]*data[[:space:]]+ci_pinned_toolchain' "$rustup_dag"; then + echo "::error::dsl/extdeps/rustup.dag declares ci_pinned_toolchain — retired duplicate authority symbol. Use rust-toolchain.toml only." + exit 1 +fi + +echo "Rust toolchain single-authority check OK (channel=${channel}; no duplicate literal or ci_pinned_toolchain in rustup.dag)." diff --git a/scripts/slow-test-exemptions.txt b/scripts/slow-test-exemptions.txt index 482d032a3e0..6b2c0bfefbb 100644 --- a/scripts/slow-test-exemptions.txt +++ b/scripts/slow-test-exemptions.txt @@ -78,6 +78,10 @@ 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. +<<<<<<< HEAD t_demo_fixture_test::t_demo_canonical_suites_are_runner_visible # ROADMAP T-Demo (Lane M): cold `compile_to_dag` on full `t_demo_fixtures.dag` + two `TestRunner::run_suite` passes; ~2.6–3.3s wall on isolated/filtered runs (above 2s Phase-0 ratchet). `compile_fixture` uses `cached_compile_to_dag`; module `OnceLock` still shares one `Dag` across sibling tests. Dissolution: delete this line and lower `TEST_TIMEOUT_MAX_EXEMPTIONS` in the same commit once `RUSTC_BOOTSTRAP=1 cargo test -p v3-compiler t_demo_canonical_suites_are_runner_visible -- -Z unstable-options --report-time` shows <=2000ms wall on cold ubuntu-latest after fixture slimming or shared-runner warming (TESTING.md). +======= +t_demo_fixture_test::t_demo_canonical_suites_are_runner_visible # ROADMAP T-Demo: `compile_to_dag` on the skeleton + `TestRunner::run_suite` for two canonical suites sits above the 2s Phase-0 ratchet in isolation on cold ubuntu-latest (PR #1794); `OnceLock` in `integration.rs` dedupes the compile across sibling T-Demo tests. Dissolution: delete this line and lower `TEST_TIMEOUT_MAX_EXEMPTIONS` in the same commit once `RUSTC_BOOTSTRAP=1 cargo test -p v3-compiler t_demo_canonical_suites_are_runner_visible -- -Z unstable-options --report-time` shows <=2000ms wall on cold ubuntu-latest after fixture slimming or shared-bootstrap warming (TESTING.md). +>>>>>>> 39f611d04 (chore(ci): tighten toolchain guard + exemption dissolution text) 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. From 82a13e756b13f14c2f2fbba6fe2609292fee92dc Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 19:11:09 +0000 Subject: [PATCH 08/17] chore(ci): tighten toolchain guard + exemption dissolution text - check-rust-toolchain-single-authority: parse channel from rust-toolchain.toml and fail if dsl/extdeps/rustup.dag contains that quoted literal (closes the ci_channel rename bypass); keep ci_pinned_toolchain as a regression guard. - slow-test-exemptions: name a checkable removal condition for the T-Demo canonical suite ratchet line (gpt-5-5-pro APPROVE_WITH_COMMENTS). Co-authored-by: Cursor --- scripts/slow-test-exemptions.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/slow-test-exemptions.txt b/scripts/slow-test-exemptions.txt index 6b2c0bfefbb..482d032a3e0 100644 --- a/scripts/slow-test-exemptions.txt +++ b/scripts/slow-test-exemptions.txt @@ -78,10 +78,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. -<<<<<<< HEAD t_demo_fixture_test::t_demo_canonical_suites_are_runner_visible # ROADMAP T-Demo (Lane M): cold `compile_to_dag` on full `t_demo_fixtures.dag` + two `TestRunner::run_suite` passes; ~2.6–3.3s wall on isolated/filtered runs (above 2s Phase-0 ratchet). `compile_fixture` uses `cached_compile_to_dag`; module `OnceLock` still shares one `Dag` across sibling tests. Dissolution: delete this line and lower `TEST_TIMEOUT_MAX_EXEMPTIONS` in the same commit once `RUSTC_BOOTSTRAP=1 cargo test -p v3-compiler t_demo_canonical_suites_are_runner_visible -- -Z unstable-options --report-time` shows <=2000ms wall on cold ubuntu-latest after fixture slimming or shared-runner warming (TESTING.md). -======= -t_demo_fixture_test::t_demo_canonical_suites_are_runner_visible # ROADMAP T-Demo: `compile_to_dag` on the skeleton + `TestRunner::run_suite` for two canonical suites sits above the 2s Phase-0 ratchet in isolation on cold ubuntu-latest (PR #1794); `OnceLock` in `integration.rs` dedupes the compile across sibling T-Demo tests. Dissolution: delete this line and lower `TEST_TIMEOUT_MAX_EXEMPTIONS` in the same commit once `RUSTC_BOOTSTRAP=1 cargo test -p v3-compiler t_demo_canonical_suites_are_runner_visible -- -Z unstable-options --report-time` shows <=2000ms wall on cold ubuntu-latest after fixture slimming or shared-bootstrap warming (TESTING.md). ->>>>>>> 39f611d04 (chore(ci): tighten toolchain guard + exemption dissolution text) 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. From 4f1e61f1967484a08fddbf2d3540e19f48c8e69d Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 20:12:44 +0000 Subject: [PATCH 09/17] ci(self_host_ratchet): raise job timeout to 60m The job waits on `v3` (~27m cold) then runs release `determinism_test` and `self_host_fixed_point`. The previous 30m job wall cancelled the fixed-point step mid-run (PR #1794 CI), yielding a cancelled check. Double the job timeout so the staged DB-8 steps can finish. Co-authored-by: Cursor --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7018e151445..31836ee3c7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -362,8 +362,8 @@ jobs: # merge-blocking before Lane 1e graduation. if: github.event_name == 'push' && github.ref == 'refs/heads/main' # Same sizing rationale as `v3` above: DB-8 release builds run on - # ubicloud-standard-8. Use a 60m wall-clock cap so cold-cache / full release - # `cargo` work is unlikely to hit mid-compile cancellation (ratchet remains non-blocking). + # `ubicloud-standard-8` (#1814). **60m** wall so cold-cache / full release `cargo` + # work is unlikely to hit mid-compile cancellation (#1794); ratchet remains non-blocking. runs-on: ubicloud-standard-8 timeout-minutes: 60 needs: [v3] From 59a6fa9f7a62a6c17557a94847bd496ecc6fcc8d Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 21:23:29 +0000 Subject: [PATCH 10/17] ci: extend toolchain guard to forbid explicit workflow toolchain input actions-rust-lang/setup-rust-toolchain ignores rust-toolchain.toml when `toolchain:` is set under `with:`. Fail closed on any indented YAML `toolchain:` key in .github/workflows/ci.yml (gpt-5-5-pro APPROVE_WITH_COMMENTS). Co-authored-by: Cursor --- .../check-rust-toolchain-single-authority.sh | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/scripts/check-rust-toolchain-single-authority.sh b/scripts/check-rust-toolchain-single-authority.sh index 0738ddbeb22..c41c601af70 100755 --- a/scripts/check-rust-toolchain-single-authority.sh +++ b/scripts/check-rust-toolchain-single-authority.sh @@ -5,8 +5,12 @@ # reintroduce that literal (any `data … = ""` or comment drift) nor the # retired `ci_pinned_toolchain` symbol — see PR #1794 / INVARIANTS P2. # -# Dissolution: delete this script and its CI step if extdeps gains a generated -# conformance link to rust-toolchain.toml instead of this guard. +# Also fail if `.github/workflows/ci.yml` sets an explicit `toolchain:` input on +# `actions-rust-lang/setup-rust-toolchain` (the action ignores rust-toolchain.toml +# when that input is present — same authority drift class). +# +# Dissolution: delete this script and its CI step if extdeps + workflow toolchain +# selection are generated or schema-checked so this shell guard is redundant. set -euo pipefail @@ -14,6 +18,7 @@ script_dir=$(cd "$(dirname "$0")" && pwd) repo_root=$(cd "$script_dir/.." && pwd) rustup_dag="$repo_root/dsl/extdeps/rustup.dag" toolchain_toml="$repo_root/rust-toolchain.toml" +ci_yml="$repo_root/.github/workflows/ci.yml" if [ ! -r "$rustup_dag" ]; then echo "::error::missing $rustup_dag" @@ -25,6 +30,11 @@ if [ ! -r "$toolchain_toml" ]; then exit 2 fi +if [ ! -r "$ci_yml" ]; then + echo "::error::missing $ci_yml" + exit 2 +fi + if ! grep -Eq '^[[:space:]]*channel[[:space:]]*=[[:space:]]*"' "$toolchain_toml"; then echo "::error::rust-toolchain.toml must contain a quoted [toolchain].channel line" exit 1 @@ -49,4 +59,11 @@ if grep -Eq '^[[:space:]]*data[[:space:]]+ci_pinned_toolchain' "$rustup_dag"; th exit 1 fi -echo "Rust toolchain single-authority check OK (channel=${channel}; no duplicate literal or ci_pinned_toolchain in rustup.dag)." +# Indented YAML key `toolchain:` under a `with:` block would make setup-rust-toolchain +# ignore rust-toolchain.toml — forbid it (comments must not fake this shape at BOL). +if grep -Eq '^[[:space:]]+toolchain[[:space:]]*:' "$ci_yml"; then + echo "::error::.github/workflows/ci.yml contains an explicit \`toolchain:\` input — rust-toolchain.toml would be ignored. Remove it from setup-rust-toolchain steps." + exit 1 +fi + +echo "Rust toolchain single-authority check OK (channel=${channel}; rustup.dag + ci.yml guard)." From 5fd1d3f371fc652234054eed8f9ded3aff814a49 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 22:27:51 +0000 Subject: [PATCH 11/17] docs(ci): align rust setup comment with toolchain guard The workflow comment previously suggested adding an explicit `toolchain:` input for non-default working-directory setups, but check-rust-toolchain-single-authority.sh forbids that (and the action would ignore rust-toolchain.toml). Point operators at copying the file instead (gpt-5-5-pro APPROVE_WITH_COMMENTS). Co-authored-by: Cursor --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31836ee3c7f..3d6034b5b66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,8 +31,10 @@ concurrency: # Rust: each `setup-rust-toolchain` step omits `toolchain:` so the action reads # `rust-toolchain.toml` at the repo root after `actions/checkout`. No job uses a -# non-default `working-directory` for Rust setup; if one did, you would need an -# explicit `toolchain:` input or a copy of the file inside that directory. +# non-default `working-directory` for Rust setup; if one did, make `rust-toolchain.toml` +# visible from that directory (e.g. copy it in) — do **not** add a workflow `toolchain:` +# input: `scripts/check-rust-toolchain-single-authority.sh` rejects it, and the action +# ignores the repo file when `toolchain` is set explicitly (upstream semantics). env: CARGO_TERM_COLOR: always RUSTFLAGS: -D warnings From 7f04c4e1479dc8f81611eb74d5a6495886001048 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 5 May 2026 22:54:49 +0000 Subject: [PATCH 12/17] ci: match rustup.dag guard to semver bare-token drift Extend check-rust-toolchain-single-authority to reject the bare parsed channel in rustup.dag when the channel looks semver-like (starts with digit.digit), closing the quoted-only gap for unquoted comment drift. Word channels stay quoted-only to avoid stable-channel prose false positives. APPROVE_WITH_COMMENTS follow-up (gpt-5-5-pro on PR #1794). Co-authored-by: Cursor --- scripts/check-rust-toolchain-single-authority.sh | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/check-rust-toolchain-single-authority.sh b/scripts/check-rust-toolchain-single-authority.sh index c41c601af70..67224266683 100755 --- a/scripts/check-rust-toolchain-single-authority.sh +++ b/scripts/check-rust-toolchain-single-authority.sh @@ -2,8 +2,10 @@ # # P2 single-authority: the pinned rustc channel string lives only in # `rust-toolchain.toml` `[toolchain].channel`. `dsl/extdeps/rustup.dag` must not -# reintroduce that literal (any `data … = ""` or comment drift) nor the -# retired `ci_pinned_toolchain` symbol — see PR #1794 / INVARIANTS P2. +# reintroduce that value as a quoted literal, as a bare semver-like token (catches +# unquoted comment drift such as `// pin 1.93.0`), nor via the retired +# `ci_pinned_toolchain` symbol — see PR #1794 / INVARIANTS P2. Word channels (e.g. +# `stable`) are only checked in quoted form to avoid unrelated prose false positives. # # Also fail if `.github/workflows/ci.yml` sets an explicit `toolchain:` input on # `actions-rust-lang/setup-rust-toolchain` (the action ignores rust-toolchain.toml @@ -54,6 +56,16 @@ if grep -Fq "$quoted_channel" "$rustup_dag"; then exit 1 fi +# Semver-like channels: also reject the bare token so unquoted comment/data drift +# cannot reintroduce the pin (e.g. `// use 1.93.0`). Skipped for word channels like +# `stable` where this substring can appear in unrelated prose. +if [[ "$channel" =~ ^[0-9]+\.[0-9]+ ]]; then + if grep -Fq "$channel" "$rustup_dag"; then + echo "::error::dsl/extdeps/rustup.dag contains bare channel token '${channel}' — duplicate authority (keep the channel only in rust-toolchain.toml)." + exit 1 + fi +fi + if grep -Eq '^[[:space:]]*data[[:space:]]+ci_pinned_toolchain' "$rustup_dag"; then echo "::error::dsl/extdeps/rustup.dag declares ci_pinned_toolchain — retired duplicate authority symbol. Use rust-toolchain.toml only." exit 1 From 16ba8328c2ab8e826276a47ca144ce24dcfdb58a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 04:54:06 +0000 Subject: [PATCH 13/17] ci(self_host_ratchet): run on ubicloud-standard-8 like main Release determinism + self_host_fixed_point compile was interrupted by a runner shutdown on PR #1794 (not an assertion failure). Restore the 8-vCPU runner label used on main for this job; it is independent of P2 rust-toolchain.toml single-authority (no workflow toolchain: input). Co-authored-by: Cursor --- .github/workflows/ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d6034b5b66..c60e28362b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -363,9 +363,11 @@ jobs: # PR job still reports as a failed check, which makes this advisory ratchet # merge-blocking before Lane 1e graduation. if: github.event_name == 'push' && github.ref == 'refs/heads/main' - # Same sizing rationale as `v3` above: DB-8 release builds run on - # `ubicloud-standard-8` (#1814). **60m** wall so cold-cache / full release `cargo` - # work is unlikely to hit mid-compile cancellation (#1794); ratchet remains non-blocking. + # Same sizing rationale as `v3` above: DB-8 release builds + determinism_test (5× matrix) + + # `self_host_fixed_point` run on `ubicloud-standard-8` (#1814); small/default runners saw + # mid-compile shutdown on #1794. **60m** wall so cold-cache / full release `cargo` is unlikely + # to cancel mid-job; ratchet remains non-blocking. (Runner label is orthogonal to P2: + # `rust-toolchain.toml` + no workflow `toolchain:` input.) runs-on: ubicloud-standard-8 timeout-minutes: 60 needs: [v3] From d18e2acb4c52246b12532d021bbc5c862eb99a46 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 05:16:08 +0000 Subject: [PATCH 14/17] ci: scan all GitHub workflow YAML for toolchain: drift Composer-2 exploratory on #1794: the P2 guard only inspected ci.yml. Walk .github/workflows/*.yml and *.yaml so a split CI cannot bypass the check; repo currently has only ci.yml. Align workflow header comment. Co-authored-by: Cursor --- .github/workflows/ci.yml | 3 +- .../check-rust-toolchain-single-authority.sh | 29 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c60e28362b3..aadc4c84f67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,8 @@ concurrency: # `rust-toolchain.toml` at the repo root after `actions/checkout`. No job uses a # non-default `working-directory` for Rust setup; if one did, make `rust-toolchain.toml` # visible from that directory (e.g. copy it in) — do **not** add a workflow `toolchain:` -# input: `scripts/check-rust-toolchain-single-authority.sh` rejects it, and the action +# input: `scripts/check-rust-toolchain-single-authority.sh` rejects it in any +# `.github/workflows/*.{yml,yaml}`, and the action # ignores the repo file when `toolchain` is set explicitly (upstream semantics). env: CARGO_TERM_COLOR: always diff --git a/scripts/check-rust-toolchain-single-authority.sh b/scripts/check-rust-toolchain-single-authority.sh index 67224266683..a8e882eddfd 100755 --- a/scripts/check-rust-toolchain-single-authority.sh +++ b/scripts/check-rust-toolchain-single-authority.sh @@ -7,9 +7,9 @@ # `ci_pinned_toolchain` symbol — see PR #1794 / INVARIANTS P2. Word channels (e.g. # `stable`) are only checked in quoted form to avoid unrelated prose false positives. # -# Also fail if `.github/workflows/ci.yml` sets an explicit `toolchain:` input on -# `actions-rust-lang/setup-rust-toolchain` (the action ignores rust-toolchain.toml -# when that input is present — same authority drift class). +# Also fail if any `.github/workflows/*.{yml,yaml}` sets an explicit `toolchain:` +# input on `actions-rust-lang/setup-rust-toolchain` (the action ignores +# rust-toolchain.toml when that input is present — same authority drift class). # # Dissolution: delete this script and its CI step if extdeps + workflow toolchain # selection are generated or schema-checked so this shell guard is redundant. @@ -20,7 +20,7 @@ script_dir=$(cd "$(dirname "$0")" && pwd) repo_root=$(cd "$script_dir/.." && pwd) rustup_dag="$repo_root/dsl/extdeps/rustup.dag" toolchain_toml="$repo_root/rust-toolchain.toml" -ci_yml="$repo_root/.github/workflows/ci.yml" +workflows_dir="$repo_root/.github/workflows" if [ ! -r "$rustup_dag" ]; then echo "::error::missing $rustup_dag" @@ -32,8 +32,8 @@ if [ ! -r "$toolchain_toml" ]; then exit 2 fi -if [ ! -r "$ci_yml" ]; then - echo "::error::missing $ci_yml" +if [ ! -d "$workflows_dir" ]; then + echo "::error::missing $workflows_dir" exit 2 fi @@ -73,9 +73,18 @@ fi # Indented YAML key `toolchain:` under a `with:` block would make setup-rust-toolchain # ignore rust-toolchain.toml — forbid it (comments must not fake this shape at BOL). -if grep -Eq '^[[:space:]]+toolchain[[:space:]]*:' "$ci_yml"; then - echo "::error::.github/workflows/ci.yml contains an explicit \`toolchain:\` input — rust-toolchain.toml would be ignored. Remove it from setup-rust-toolchain steps." - exit 1 +shopt -s nullglob +workflow_files=("$workflows_dir"/*.yml "$workflows_dir"/*.yaml) +if [ "${#workflow_files[@]}" -eq 0 ]; then + echo "::error::no *.yml or *.yaml under $workflows_dir" + exit 2 fi +for wf in "${workflow_files[@]}"; do + if grep -Eq '^[[:space:]]+toolchain[[:space:]]*:' "$wf"; then + rel=${wf#"$repo_root/"} + echo "::error::${rel} contains an explicit \`toolchain:\` input — rust-toolchain.toml would be ignored. Remove it from setup-rust-toolchain steps." + exit 1 + fi +done -echo "Rust toolchain single-authority check OK (channel=${channel}; rustup.dag + ci.yml guard)." +echo "Rust toolchain single-authority check OK (channel=${channel}; rustup.dag + workflow guard)." From 956c360ae71fca22616059919c7c1f0f1c0893cd Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 06:28:39 +0000 Subject: [PATCH 15/17] ci: scope workflow toolchain guard to setup-rust-toolchain steps Codex #1794 feedback: file-wide indented `toolchain:` grep was a string-heuristic that could false-fail unrelated Actions keys. Walk each `toolchain:` line up to the enclosing step (`-` at lesser indent) and only error when that step block contains actions-rust-lang/setup-rust-toolchain (Python3; same CI images). Update ci.yml header comment to match. Co-authored-by: Cursor --- .github/workflows/ci.yml | 3 +- .../check-rust-toolchain-single-authority.sh | 77 +++++++++++++++---- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aadc4c84f67..43e4857920a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,8 @@ concurrency: # `rust-toolchain.toml` at the repo root after `actions/checkout`. No job uses a # non-default `working-directory` for Rust setup; if one did, make `rust-toolchain.toml` # visible from that directory (e.g. copy it in) — do **not** add a workflow `toolchain:` -# input: `scripts/check-rust-toolchain-single-authority.sh` rejects it in any +# input: `scripts/check-rust-toolchain-single-authority.sh` rejects `toolchain:` in the +# same Actions step as `actions-rust-lang/setup-rust-toolchain` in any # `.github/workflows/*.{yml,yaml}`, and the action # ignores the repo file when `toolchain` is set explicitly (upstream semantics). env: diff --git a/scripts/check-rust-toolchain-single-authority.sh b/scripts/check-rust-toolchain-single-authority.sh index a8e882eddfd..212934961fd 100755 --- a/scripts/check-rust-toolchain-single-authority.sh +++ b/scripts/check-rust-toolchain-single-authority.sh @@ -7,8 +7,8 @@ # `ci_pinned_toolchain` symbol — see PR #1794 / INVARIANTS P2. Word channels (e.g. # `stable`) are only checked in quoted form to avoid unrelated prose false positives. # -# Also fail if any `.github/workflows/*.{yml,yaml}` sets an explicit `toolchain:` -# input on `actions-rust-lang/setup-rust-toolchain` (the action ignores +# Also fail if any `.github/workflows/*.{yml,yaml}` pairs `toolchain:` with +# `actions-rust-lang/setup-rust-toolchain` in the same step (the action ignores # rust-toolchain.toml when that input is present — same authority drift class). # # Dissolution: delete this script and its CI step if extdeps + workflow toolchain @@ -71,20 +71,67 @@ if grep -Eq '^[[:space:]]*data[[:space:]]+ci_pinned_toolchain' "$rustup_dag"; th exit 1 fi -# Indented YAML key `toolchain:` under a `with:` block would make setup-rust-toolchain -# ignore rust-toolchain.toml — forbid it (comments must not fake this shape at BOL). -shopt -s nullglob -workflow_files=("$workflows_dir"/*.yml "$workflows_dir"/*.yaml) -if [ "${#workflow_files[@]}" -eq 0 ]; then - echo "::error::no *.yml or *.yaml under $workflows_dir" +# Indented YAML key `toolchain:` under `with:` for `actions-rust-lang/setup-rust-toolchain` +# ignores rust-toolchain.toml — forbid that pairing only (not unrelated `toolchain:` keys +# in other actions). Implemented with a small Python scan (bounded step walk); see PR #1794 +# codex review (narrow authority boundary vs raw file-wide grep). +if ! command -v python3 >/dev/null 2>&1; then + echo "::error::python3 is required for workflow toolchain guard (setup-rust-toolchain scope)" exit 2 fi -for wf in "${workflow_files[@]}"; do - if grep -Eq '^[[:space:]]+toolchain[[:space:]]*:' "$wf"; then - rel=${wf#"$repo_root/"} - echo "::error::${rel} contains an explicit \`toolchain:\` input — rust-toolchain.toml would be ignored. Remove it from setup-rust-toolchain steps." - exit 1 - fi -done + +python3 - "$workflows_dir" "$repo_root" <<'PY' +import pathlib +import re +import sys + +SETUP = "actions-rust-lang/setup-rust-toolchain" + + +def violation_in_file(wf_path: pathlib.Path): + lines = wf_path.read_text(encoding="utf-8").splitlines() + for i, line in enumerate(lines): + m_tc = re.match(r"^(\s+)toolchain\s*:", line) + if not m_tc: + continue + tc_ws = len(m_tc.group(1)) + j = i - 1 + while j >= 0: + m_dash = re.match(r"^(\s*)-\s", lines[j]) + if m_dash is not None and len(m_dash.group(1)) < tc_ws: + break + j -= 1 + if j < 0: + continue + block = "\n".join(lines[j : i + 1]) + if SETUP in block: + return i + 1, line.strip() + return None + + +def main() -> int: + workflows_dir = pathlib.Path(sys.argv[1]) + repo_root = pathlib.Path(sys.argv[2]) + files = sorted(workflows_dir.glob("*.yml")) + sorted(workflows_dir.glob("*.yaml")) + if not files: + print(f"::error::no *.yml or *.yaml under {workflows_dir}") + return 2 + for wf in files: + hit = violation_in_file(wf) + if hit is not None: + lineno, preview = hit + rel = wf.resolve().relative_to(repo_root.resolve()) + print( + f"::error::file={rel},line={lineno}::explicit `toolchain:` input on " + f"{SETUP} — rust-toolchain.toml would be ignored. Remove it from that step's `with:`." + ) + print(f"{rel}:{lineno}: {preview}") + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +PY echo "Rust toolchain single-authority check OK (channel=${channel}; rustup.dag + workflow guard)." From 870962258b70792b923532d5ad69426bea0c8c1b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 06:42:20 +0000 Subject: [PATCH 16/17] docs(db-8): align fixed-point ratchet CI with ci.yml Composer-2 exploratory: design-fixed-point-ratchet.md still described a generic self_host job (ubuntu, old needs, cargo run without -p) and PR+main scheduling. Update CI integration, acceptance checklist, rejected-alternatives note, and local run examples to match self_host_ratchet on main pushes + v3 determinism on PRs. Co-authored-by: Cursor --- docs/design-fixed-point-ratchet.md | 45 ++++++++++-------------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/docs/design-fixed-point-ratchet.md b/docs/design-fixed-point-ratchet.md index 70c6f5dde79..e31f666f234 100644 --- a/docs/design-fixed-point-ratchet.md +++ b/docs/design-fixed-point-ratchet.md @@ -88,32 +88,15 @@ Comparison uses raw byte equality — whitespace matters, line endings matter. M ### CI integration -`.github/workflows/ci.yml` — add a new job: - -```yaml -self_host: - runs-on: ubuntu-latest - timeout-minutes: 15 - needs: [ci, v3] # runs after basic build + v3 tests pass - steps: - - uses: actions/checkout@v4 - - uses: actions-rust-lang/setup-rust-toolchain@v1.16.0 - with: - cache: false - rustflags: "" - - name: Cache Cargo (self_host) - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - target/ - key: cargo-self-host-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('src/v3/compiler/**') }} - - name: Self-host fixed-point check - run: cargo run --bin self_host_fixed_point --release -``` +Canonical wiring is **`.github/workflows/ci.yml`**, job **`self_host_ratchet`** (`needs: [v3]`). + +While DB-8 stays **staged** and the job remains **`continue-on-error: true`**, it is scheduled on **`push` to `refs/heads/main` only** (PR runs were producing merge-blocking red checks when Actions cancelled in-flight jobs). The **`v3`** job still runs **`determinism_test`** on pull requests. + +- **Runner / wall clock:** `ubicloud-standard-8`, `timeout-minutes: 60` (heavy release work + cold cache). +- **Rust setup:** `actions-rust-lang/setup-rust-toolchain@v1.16.0` with **`toolchain:` omitted** so the action reads `rust-toolchain.toml` (enforced by `scripts/check-rust-toolchain-single-authority.sh`). +- **Steps (release):** `cargo test -p v3-compiler --release --test determinism_test`, then `cargo run -p v3-compiler --release --bin self_host_fixed_point`, plus informational `emit.rs` HashMap/HashSet grep. -Runs after the core `v3` job. If emission changes, this fails; CI blocks merge. +Failures are **advisory** until Lane 1e graduates the ratchet to merge-blocking. ### Sources of non-determinism (to eliminate) @@ -223,7 +206,7 @@ Runs per-test, local to each emit call. Catches non-determinism without needing **Why per-fixture 5x re-run test?** Because most non-determinism manifests within a single process (HashMap seed randomness). 5 runs gives high confidence without absurd test runtime. -**Why separate `self_host_fixed_point` binary, not a test?** Because it's a pipeline (emit → rustc → run → diff) too heavyweight for `cargo test`. CI runs it as its own job; developers can invoke it locally via `cargo run --bin self_host_fixed_point --release`. +**Why separate `self_host_fixed_point` binary, not a test?** Because it's a pipeline (emit → rustc → run → diff) too heavyweight for `cargo test`. CI runs it as its own job; developers can invoke it locally via `cargo run -p v3-compiler --release --bin self_host_fixed_point`. --- @@ -233,7 +216,7 @@ Runs per-test, local to each emit call. Catches non-determinism without needing **Structural diff (parse both, compare ASTs)** — allows whitespace differences. Too lenient. We want bit-identical as the contract. Rejected. -**Only check on push to main** — misses PRs that introduce non-determinism. Run on every PR + main push. Rejected "only on main." +**Only check on push to main** — misses PRs that introduce non-determinism if it were the *sole* long-term policy. **Current compromise (staged):** `self_host_ratchet` runs on **`main` pushes only** while the job is still advisory, because cancelled PR workflow runs were surfacing as merge-blocking failures; PRs still get **`determinism_test`** inside **`v3`**. Re-open PR+`main` coverage when the ratchet graduates merge-blocking. **Accept "close enough" — diff lines < 5** — opens a hole. Any intentional emission change must be accompanied by snapshot update; any unintentional change is a bug. Rejected. @@ -256,7 +239,7 @@ target/self_host/ ### Performance -Self-host cycle takes: emit (2s) + rustc (30s) + run (1s) + diff (instant) = ~33s per cycle. Running once per PR is acceptable. If it grows to the v2 "20-minute self-compile" regime (THESIS.md §merge_envs case study), sound alarm — see open question 2 below. +Self-host cycle takes: emit (2s) + rustc (30s) + run (1s) + diff (instant) = ~33s per cycle on a warm machine. CI runs the dedicated job on each **`main` push** while staged (see **CI integration**). If it grows to the v2 "20-minute self-compile" regime (THESIS.md §merge_envs case study), sound alarm — see open question 2 below. ### Bisecting non-determinism @@ -277,7 +260,7 @@ Maintain a `docs/self-host-incidents.md` log of found non-determinism sources - **`src/v3/compiler/compiler.dag`** — the compiler source being cycled (PR #418 and later additions) - **Create `src/v3/compiler/src/bin/self_host_fixed_point.rs`** — the CI binary - **Create `src/v3/compiler/tests/determinism_test.rs`** (+ shared matrix in `tests/common/determinism_fixtures.rs`) — per-fixture 5× determinism check -- **Update `.github/workflows/ci.yml`** — `self_host` job +- **Update `.github/workflows/ci.yml`** — `self_host_ratchet` job - **Update `.gitignore`** — `target/self_host/` - **Thesis anchor** — SELF_HOSTING.md §14 (fixed-point discipline) @@ -286,7 +269,7 @@ Maintain a `docs/self-host-incidents.md` log of found non-determinism sources ## Acceptance (Lane 3 Stage 3c owns) - [ ] `self_host_fixed_point` binary passes full emit → rustc → run → **byte-identical** diff on `dsl/gunbc/compiler.dag` (staged until v3 parses + emits a CLI-shaped crate) -- [x] CI job `self_host_ratchet` runs after `v3` on every PR + main push; **`continue-on-error: true`** until Lane 1e closes (then graduate to merge-blocking) +- [x] CI job `self_host_ratchet` runs after `v3` on each **`main` branch push** while staged (PRs: `determinism_test` in **`v3`**); **`continue-on-error: true`** until Lane 1e closes (then graduate to merge-blocking) - [x] `tests/determinism_test.rs` passes per-matrix-row 5× equivalence (Rust full matrix; Go/Python scoped per `determinism_fixtures.rs`) - [x] Informational grep step in `self_host_ratchet` surfaces `HashMap`/`HashSet::` in `emit.rs` (strict gate deferred until Lane 1e clears iteration debt) - [x] Invariant D-1 (determinism) added to `INVARIANTS.md` @@ -295,7 +278,7 @@ Maintain a `docs/self-host-incidents.md` log of found non-determinism sources ## Open questions -1. **Does the ratchet need to run in `release` mode?** Probably yes — rustc release inlines more, which can stabilize output. Design has `cargo run --bin self_host_fixed_point --release`. +1. **Does the ratchet need to run in `release` mode?** Probably yes — rustc release inlines more, which can stabilize output. Design has `cargo run -p v3-compiler --release --bin self_host_fixed_point`. 2. **What if self-host cycle time approaches v2's 20-min issue?** Alarm. Root-cause the slowdown (usually HashMap-dependent re-derivation, per THESIS.md case study). Don't accept >2min for the cycle. From ec2f09e7ec6fea931d6637ba53ecc9fa33e3246c Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 6 May 2026 06:58:09 +0000 Subject: [PATCH 17/17] ci: scan full Actions step for setup-rust-toolchain + toolchain: Codex non-blocking: pairing detection used lines[j:i+1], so a perverse YAML order (with.toolchain before uses:) could omit the uses line and bypass the guard. Extend each candidate step through the next sibling list item at the same indent. Co-authored-by: Cursor --- .../check-rust-toolchain-single-authority.sh | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/scripts/check-rust-toolchain-single-authority.sh b/scripts/check-rust-toolchain-single-authority.sh index 212934961fd..ba018f7696e 100755 --- a/scripts/check-rust-toolchain-single-authority.sh +++ b/scripts/check-rust-toolchain-single-authority.sh @@ -73,8 +73,9 @@ fi # Indented YAML key `toolchain:` under `with:` for `actions-rust-lang/setup-rust-toolchain` # ignores rust-toolchain.toml — forbid that pairing only (not unrelated `toolchain:` keys -# in other actions). Implemented with a small Python scan (bounded step walk); see PR #1794 -# codex review (narrow authority boundary vs raw file-wide grep). +# in other actions). Python scan walks each `toolchain:` line up to its Actions step head, +# then scans the **full** step (through the next sibling `-` at the same list indent) so a +# pathological `with.toolchain` **before** `uses: …/setup-rust-toolchain` cannot evade the check. if ! command -v python3 >/dev/null 2>&1; then echo "::error::python3 is required for workflow toolchain guard (setup-rust-toolchain scope)" exit 2 @@ -88,7 +89,22 @@ import sys SETUP = "actions-rust-lang/setup-rust-toolchain" -def violation_in_file(wf_path: pathlib.Path): +def _step_span(lines, step_start): + """Return [step_start, end) line indices for one GitHub Actions `steps:` list item.""" + m0 = re.match(r"^(\s*)-\s", lines[step_start]) + if not m0: + return step_start, min(step_start + 1, len(lines)) + base = len(m0.group(1)) + k = step_start + 1 + while k < len(lines): + m = re.match(r"^(\s*)-\s", lines[k]) + if m is not None and len(m.group(1)) == base: + break + k += 1 + return step_start, k + + +def violation_in_file(wf_path): lines = wf_path.read_text(encoding="utf-8").splitlines() for i, line in enumerate(lines): m_tc = re.match(r"^(\s+)toolchain\s*:", line) @@ -103,13 +119,14 @@ def violation_in_file(wf_path: pathlib.Path): j -= 1 if j < 0: continue - block = "\n".join(lines[j : i + 1]) + _, k = _step_span(lines, j) + block = "\n".join(lines[j:k]) if SETUP in block: return i + 1, line.strip() return None -def main() -> int: +def main(): workflows_dir = pathlib.Path(sys.argv[1]) repo_root = pathlib.Path(sys.argv[2]) files = sorted(workflows_dir.glob("*.yml")) + sorted(workflows_dir.glob("*.yaml"))