Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ jobs:
echo "RUSTUP_HOME=$RUNNER_TEMP/rustup" >> "$GITHUB_ENV"
echo "MAKEFLAGS=" >> "$GITHUB_ENV"
echo "CARGO_BUILD_JOBS=4" >> "$GITHUB_ENV"
GUNBC_TOOLCHAIN_SEG=$(rustc -V 2>/dev/null | tr ' ' '-'); if [ -z "$GUNBC_TOOLCHAIN_SEG" ]; then GUNBC_TOOLCHAIN_SEG=toolchain-unresolved; fi
mkdir -p "$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG"
echo "GUNBC_NATIVE_CACHE_ROOT=$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG" >> "$GITHUB_ENV"
if sccache --show-stats >/dev/null 2>&1; then
echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
echo "CARGO_INCREMENTAL=0" >> "$GITHUB_ENV"
Expand All @@ -51,6 +48,14 @@ jobs:
rustflags: ''
env:
HOME: ${{ runner.temp }}
- name: Derive native-cache root (rustc-version segment; after setup-rust-toolchain)
run: |
# 🟡 dissolve-on: ci_native_cache_root_script — orch-emitted foreign-executor prelude step deriving GUNBC_NATIVE_CACHE_ROOT from rustc -V after setup-rust-toolchain; leaf rustc/tr/mkdir/echo strings remain until typed toolchain probe lands on host_effect_apply (#5828 / shell→intent Phase 2)
if ! GUNBC_TOOLCHAIN_SEG_RAW=$(rustc -V 2>/dev/null); then echo "::error::rustc -V failed after setup-rust-toolchain: cannot derive native-cache toolchain segment"; exit 1; fi
GUNBC_TOOLCHAIN_SEG=$(echo "$GUNBC_TOOLCHAIN_SEG_RAW" | tr ' ' '-')
if [ -z "$GUNBC_TOOLCHAIN_SEG" ]; then echo "::error::rustc -V produced an empty native-cache toolchain segment after setup-rust-toolchain"; exit 1; fi
mkdir -p "$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG"
echo "GUNBC_NATIVE_CACHE_ROOT=$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG" >> "$GITHUB_ENV"
- name: Pin rustup default (isolated RUSTUP_HOME has no default toolchain)
run: |
rustup default "$(rustup show active-toolchain | awk '{print $1; exit}')"
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/falsifier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ jobs:
echo "RUSTUP_HOME=$RUNNER_TEMP/rustup" >> "$GITHUB_ENV"
echo "MAKEFLAGS=" >> "$GITHUB_ENV"
echo "CARGO_BUILD_JOBS=4" >> "$GITHUB_ENV"
GUNBC_TOOLCHAIN_SEG=$(rustc -V 2>/dev/null | tr ' ' '-'); if [ -z "$GUNBC_TOOLCHAIN_SEG" ]; then GUNBC_TOOLCHAIN_SEG=toolchain-unresolved; fi
mkdir -p "$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG"
echo "GUNBC_NATIVE_CACHE_ROOT=$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG" >> "$GITHUB_ENV"
if sccache --show-stats >/dev/null 2>&1; then
echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
echo "CARGO_INCREMENTAL=0" >> "$GITHUB_ENV"
Expand All @@ -45,6 +42,14 @@ jobs:
rustflags: ''
env:
HOME: ${{ runner.temp }}
- name: Derive native-cache root (rustc-version segment; after setup-rust-toolchain)
run: |
# 🟡 dissolve-on: ci_native_cache_root_script — orch-emitted foreign-executor prelude step deriving GUNBC_NATIVE_CACHE_ROOT from rustc -V after setup-rust-toolchain; leaf rustc/tr/mkdir/echo strings remain until typed toolchain probe lands on host_effect_apply (#5828 / shell→intent Phase 2)
if ! GUNBC_TOOLCHAIN_SEG_RAW=$(rustc -V 2>/dev/null); then echo "::error::rustc -V failed after setup-rust-toolchain: cannot derive native-cache toolchain segment"; exit 1; fi
GUNBC_TOOLCHAIN_SEG=$(echo "$GUNBC_TOOLCHAIN_SEG_RAW" | tr ' ' '-')
if [ -z "$GUNBC_TOOLCHAIN_SEG" ]; then echo "::error::rustc -V produced an empty native-cache toolchain segment after setup-rust-toolchain"; exit 1; fi
mkdir -p "$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG"
echo "GUNBC_NATIVE_CACHE_ROOT=$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG" >> "$GITHUB_ENV"
- name: Pin rustup default (isolated RUSTUP_HOME has no default toolchain)
run: |
rustup default "$(rustup show active-toolchain | awk '{print $1; exit}')"
Expand Down
27 changes: 27 additions & 0 deletions dag/gunbc/ci_workflow.dag
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import v2.workflow.ci_floor_peak_emit {
}
import v2.workflow.ci_workflow_run_emit {
ci_isolate_toolchain_script,
ci_native_cache_root_script,
ci_pin_rustup_default_script,
ci_selection_control_script
}
Expand Down Expand Up @@ -109,6 +110,31 @@ fn ci_isolate_toolchain_step() -> Step {
}
}

data ci_native_cache_derive_step_name: String = "Derive native-cache root (rustc-version segment; after setup-rust-toolchain)"

fn ci_native_cache_root_step() -> Step {
RunStep {
name: Present { value: ci_native_cache_derive_step_name },
id: none,
run: ci_native_cache_root_script(),
shell: none,
env: none,
working_directory: none,
if_condition: none,
continue_on_error: none,
timeout_minutes: none
}
}

fn ci_native_cache_prelude_order_bridge() -> String {
concat(
concat("HOME: ", ci_setup_rust_home()),
concat("\n - name: ", ci_native_cache_derive_step_name)
)
}

data ci_native_cache_prelude_order_bridge_note: String = "Adjacency-as-proxy ordering witness (not a structural Step-order predicate): ci_native_cache_prelude_order_bridge builds the setup-rust HOME env line immediately followed by the native-cache step name at the emitter's six-space yaml indent. witness_prelude_native_cache_root_step_follows_setup_rust asserts expected_ci_yml/falsifier_yml contain that substring. The real invariant is only that segment derivation runs after setup-rust-toolchain installs rustc; this check over-constrains to textual adjacency and can false-RED on a legitimate step inserted between setup-rust and native-cache, emitter yaml reformatting, or setup-rust gaining fields after HOME — but not false-green if ordering regresses. A hand-rolled match on Step would be the structural alternative; non-fold-residue forbids the wildcard arms that predicate would need (receipt: nfr floor on 14bc815db4). Dissolves when prelude ordering is witnessed without coproduct-residue match arms."

fn ci_prelude_steps() -> List<Step> {
[
ci_checkout_step(),
Expand All @@ -131,6 +157,7 @@ fn ci_prelude_steps() -> List<Step> {
continue_on_error: none,
timeout_minutes: none
},
ci_native_cache_root_step(),
RunStep {
name: Present { value: "Pin rustup default (isolated RUSTUP_HOME has no default toolchain)" },
id: none,
Expand Down
79 changes: 79 additions & 0 deletions dag/test/claim/ci_native_cache_root_witness_test.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
module test.claim.ci_native_cache_root_witness

import gunbc.ci_workflow {
ci_native_cache_prelude_order_bridge,
ci_cargo_build_jobs_count
}
import gunbc.ci_yaml_emit { expected_ci_yml }
import gunbc.falsifier_workflow { expected_falsifier_yml }
import v2.workflow.ci_workflow_run_emit {
ci_isolate_toolchain_script,
ci_native_cache_root_script,
ci_native_cache_fabricated_segment_literal,
ci_native_cache_segment_script_refuses_fabrication,
ci_isolate_toolchain_script_excludes_segment_derivation,
ci_native_cache_root_shell_emit_scaffold,
ci_native_cache_root_shell_emit_dissolution_trigger
}
import std.disposition { Scaffold, Terminal }
import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly }

data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly

data ci_native_cache_root_witness_note: String = "Discriminating witnesses for the toolchain-segment reorder+refuse fix (operator 2026-07-29): the isolate prelude must NOT derive GUNBC_TOOLCHAIN_SEG from ambient rustc (that either fabricates toolchain-unresolved or silently keys on the wrong toolchain); the dedicated native-cache step after setup-rust-toolchain must refuse on rustc -V failure. RED controls perturb the good emission with the retired fabrication literal so a regression re-opens the absorbing fallback. Prelude ordering: witness_prelude_native_cache_root_step_follows_setup_rust uses ci_native_cache_prelude_order_bridge_note — adjacency proxy, safe-direction brittleness only."

test fn witness_isolate_emission_excludes_segment_derivation() -> Bool {
ci_isolate_toolchain_script_excludes_segment_derivation(
isolate_script: ci_isolate_toolchain_script(build_jobs_count: ci_cargo_build_jobs_count())
)
}

test fn witness_native_cache_root_emission_refuses_fabrication() -> Bool {
ci_native_cache_segment_script_refuses_fabrication(
script: ci_native_cache_root_script()
)
}

test fn witness_native_cache_root_fabrication_literal_red_control() -> Bool {
let good = ci_native_cache_root_script()
let bad = concat(good, concat("\n", concat("GUNBC_TOOLCHAIN_SEG=", ci_native_cache_fabricated_segment_literal)))
ci_native_cache_segment_script_refuses_fabrication(script: good) &&
!ci_native_cache_segment_script_refuses_fabrication(script: bad)
}

test fn witness_prelude_native_cache_root_step_follows_setup_rust() -> Bool {
let bridge = ci_native_cache_prelude_order_bridge()
string_contains(s: expected_ci_yml(), pattern: bridge) &&
string_contains(s: expected_falsifier_yml(), pattern: bridge)
}

test fn witness_emitted_workflows_exclude_fabricated_segment_literal() -> Bool {
!string_contains(s: expected_ci_yml(), pattern: ci_native_cache_fabricated_segment_literal) &&
!string_contains(s: expected_falsifier_yml(), pattern: ci_native_cache_fabricated_segment_literal) &&
string_contains(
s: expected_ci_yml(),
pattern: "Derive native-cache root (rustc-version segment; after setup-rust-toolchain)"
) &&
string_contains(
s: expected_falsifier_yml(),
pattern: "Derive native-cache root (rustc-version segment; after setup-rust-toolchain)"
)
}

test fn witness_native_cache_root_emission_carries_dissolve_marker() -> Bool {
string_contains(s: ci_native_cache_root_script(), pattern: ci_native_cache_root_shell_emit_dissolution_trigger)
}

test fn witness_native_cache_root_shell_emit_is_scaffold_holds() -> Bool {
match ci_native_cache_root_shell_emit_scaffold {
Scaffold { dissolves_to: _, bind: _ } => true
Terminal { reason: _ } => false
}
}

test fn witness_emitted_workflows_refuse_missing_segment_red_control() -> Bool {
let good = expected_ci_yml()
let bad = concat(good, ci_native_cache_fabricated_segment_literal)
!string_contains(s: good, pattern: ci_native_cache_fabricated_segment_literal) &&
string_contains(s: bad, pattern: ci_native_cache_fabricated_segment_literal)
}
61 changes: 56 additions & 5 deletions src/v2/workflow/ci_workflow_run_emit.dag
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import v2.std.orchestration {
}
import v2.std.collection { Absent }
import v2.std.diagnostic { Accepted, Rejected, None, Some }
import std.disposition { Disposition, Scaffold, SingleAuthority }
import std.decl_ref { DeclarationRef, WholeDeclaration }

data ci_workflow_run_emit_refused_poison: String = "__GUNBC_ORCH_EMIT_REFUSED__ ci_workflow run-step emission was rejected by v2.compiler.emit_orchestration; this marker is not valid workflow shell, so the committed ci.yml drift gate goes red rather than a hand-spelled fallback masking the refusal (DESIGN 5: refuse, never widen)\n"

Expand All @@ -26,9 +28,13 @@ data ci_isolate_echo_rustup_home_command: String = "echo \"RUSTUP_HOME=$RUNNER_T

data ci_isolate_echo_makeflags_command: String = "echo \"MAKEFLAGS=\" >> \"$GITHUB_ENV\""

data ci_native_cache_root_note: String = "Durable native-artifact cache root (extdeps.realization.native_artifact_cache, PerRunnerFilesystem): RUNNER_TOOL_CACHE survives the per-run RUNNER_TEMP wipe on self-hosted runners, so emit-on-demand cargo workspaces warm across runs (compile_skipped receipts). The cached transport rebases only the declared /tmp/gunbc_ scratch prefix onto this root; content-hash path components stay the key, so cross-branch sharing on one runner is benign-by-identity. The rustc-version segment derived at prelude time remains a coarse operator-readable partition; correctness no longer depends on it because extdeps.realization.emit_on_demand_host places the host-observed resolved build-context identity (tools + constructed environment + Cargo configuration) beneath each input realization before .native_ready is consulted. Falsifier cold control: GUNBC_CI_NATIVE_CACHE_COLD_CONTROL."
data ci_native_cache_root_note: String = "Durable native-artifact cache root (extdeps.realization.native_artifact_cache, PerRunnerFilesystem): RUNNER_TOOL_CACHE survives the per-run RUNNER_TEMP wipe on self-hosted runners, so emit-on-demand cargo workspaces warm across runs (compile_skipped receipts). The cached transport rebases only the declared /tmp/gunbc_ scratch prefix onto this root; content-hash path components stay the key, so cross-branch sharing on one runner is benign-by-identity. The rustc-version segment derived after setup-rust-toolchain remains a coarse operator-readable partition; correctness no longer depends on it because extdeps.realization.emit_on_demand_host places the host-observed resolved build-context identity (tools + constructed environment + Cargo configuration) beneath each input realization before .native_ready is consulted. Fail-closed: rustc -V must succeed — a fabricated toolchain-unresolved slug would poison every native-cache write into one shared root (DESIGN 5). Falsifier cold control: GUNBC_CI_NATIVE_CACHE_COLD_CONTROL."

data ci_native_cache_root_toolchain_segment_command: String = "GUNBC_TOOLCHAIN_SEG=$(rustc -V 2>/dev/null | tr ' ' '-'); if [ -z \"$GUNBC_TOOLCHAIN_SEG\" ]; then GUNBC_TOOLCHAIN_SEG=toolchain-unresolved; fi"
data ci_native_cache_root_toolchain_segment_capture_command: String = "if ! GUNBC_TOOLCHAIN_SEG_RAW=$(rustc -V 2>/dev/null); then echo \"::error::rustc -V failed after setup-rust-toolchain: cannot derive native-cache toolchain segment\"; exit 1; fi"

data ci_native_cache_root_toolchain_segment_transform_command: String = "GUNBC_TOOLCHAIN_SEG=$(echo \"$GUNBC_TOOLCHAIN_SEG_RAW\" | tr ' ' '-')"

data ci_native_cache_root_toolchain_segment_missing_command: String = "if [ -z \"$GUNBC_TOOLCHAIN_SEG\" ]; then echo \"::error::rustc -V produced an empty native-cache toolchain segment after setup-rust-toolchain\"; exit 1; fi"

data ci_native_cache_root_mkdir_command: String = "mkdir -p \"$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG\""

Expand Down Expand Up @@ -89,9 +95,6 @@ fn ci_isolate_toolchain_intent(build_jobs_count: Int) -> Pipeline {
ci_workflow_do(command: ci_isolate_echo_rustup_home_command),
ci_workflow_do(command: ci_isolate_echo_makeflags_command),
ci_workflow_do(command: ci_isolate_cargo_build_jobs_command(build_jobs_count: build_jobs_count)),
ci_workflow_do(command: ci_native_cache_root_toolchain_segment_command),
ci_workflow_do(command: ci_native_cache_root_mkdir_command),
ci_workflow_do(command: ci_native_cache_root_echo_command),
ci_workflow_do(command: ci_sccache_provider_shell_injection())
],
on_failure: FailFast
Expand All @@ -102,6 +105,54 @@ fn ci_isolate_toolchain_script(build_jobs_count: Int) -> String {
ci_workflow_run_emit_pipeline(p: ci_isolate_toolchain_intent(build_jobs_count: build_jobs_count))
}

fn ci_native_cache_root_intent() -> Pipeline {
Pipeline {
steps: [
ci_workflow_do(command: ci_native_cache_root_toolchain_segment_capture_command),
ci_workflow_do(command: ci_native_cache_root_toolchain_segment_transform_command),
ci_workflow_do(command: ci_native_cache_root_toolchain_segment_missing_command),
ci_workflow_do(command: ci_native_cache_root_mkdir_command),
ci_workflow_do(command: ci_native_cache_root_echo_command)
],
on_failure: FailFast
}
}

fn ci_native_cache_root_script() -> String {
concat(
concat("# ", concat(ci_native_cache_root_shell_emit_dissolution_trigger, "\n")),
ci_workflow_run_emit_pipeline(p: ci_native_cache_root_intent())
)
}

data ci_native_cache_root_shell_emit_scaffold: Disposition = Scaffold {
dissolves_to: SingleAuthority,
bind: DeclarationRef {
module_path: "v2.workflow.ci_workflow_run_emit",
decl_name: "ci_native_cache_root_script",
field: WholeDeclaration
}
}

data ci_native_cache_root_shell_emit_dissolution_trigger: String = "🟡 dissolve-on: ci_native_cache_root_script — orch-emitted foreign-executor prelude step deriving GUNBC_NATIVE_CACHE_ROOT from rustc -V after setup-rust-toolchain; leaf rustc/tr/mkdir/echo strings remain until typed toolchain probe lands on host_effect_apply (#5828 / shell→intent Phase 2)"

data ci_native_cache_fabricated_segment_literal: String = "toolchain-unresolved"

fn ci_native_cache_segment_script_refuses_fabrication(script: String) -> Bool {
string_contains(s: script, pattern: "if ! GUNBC_TOOLCHAIN_SEG_RAW=$(rustc -V") &&
string_contains(s: script, pattern: "GUNBC_TOOLCHAIN_SEG=$(echo \"$GUNBC_TOOLCHAIN_SEG_RAW\" | tr ' ' '-')") &&
string_contains(s: script, pattern: "exit 1") &&
!string_contains(s: script, pattern: ci_native_cache_fabricated_segment_literal) &&
!string_contains(s: script, pattern: "if ! GUNBC_TOOLCHAIN_SEG=$(rustc -V")
}

fn ci_isolate_toolchain_script_excludes_segment_derivation(isolate_script: String) -> Bool {
!string_contains(s: isolate_script, pattern: "rustc -V") &&
!string_contains(s: isolate_script, pattern: "GUNBC_TOOLCHAIN_SEG") &&
!string_contains(s: isolate_script, pattern: "GUNBC_NATIVE_CACHE_ROOT") &&
!string_contains(s: isolate_script, pattern: ci_native_cache_fabricated_segment_literal)
}

fn ci_pin_rustup_default_intent() -> Pipeline {
Pipeline {
steps: [
Expand Down
Loading