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
15 changes: 14 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,22 @@ jobs:
timeout-minutes: 15
ci:
runs-on: [self-hosted, linux, arm64]
needs: [build, regen]
needs: [build, regen, heal_generated_artifacts]
timeout-minutes: 110
if: "!cancelled()"
steps:
- name: "Refuse when an upstream job already established this branch is red (fails fast instead of skipping: a skipped job reports success to the required check)"
run: |
set -e
BUILD='${{ needs.build.result }}'
REGEN='${{ needs.regen.result }}'
HEAL='${{ needs.heal_generated_artifacts.result }}'
if [ "$BUILD" != "success" ] || [ "$REGEN" != "success" ] || [ "$HEAL" = "failure" ]; then
echo "::error::FloorUpstreamAlreadyRed build=$BUILD regen=$REGEN heal=$HEAL — an upstream job already established this branch is red, so the floor refuses before spending its budget. The located cause is that job's own diagnostic; this step adds no verdict of its own."
exit 1
fi
echo "[floor-preflight] upstream clean: build=$BUILD regen=$REGEN heal=$HEAL"
timeout-minutes: 5
- name: Checkout
uses: actions/checkout@v5
with:
Expand Down
26 changes: 22 additions & 4 deletions dag/gunbc/ci_workflow.dag
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import gunbc.ci_spec {
import v2.workflow.ci_heal_revalidation_preflight_emit {
ci_heal_revalidation_preflight_script,
}
import gunbc.ci_workflow_expressions { ci_deploy_push_to_main_if }
import gunbc.ci_workflow_expressions {
ci_deploy_push_to_main_if,
ci_floor_runs_unless_cancelled_if,
ci_floor_upstream_red_refusal_script
}
import gunbc.ci_heal_credential {
ci_heal_job_permissions,
ci_heal_branch_head_checkout_step,
Expand Down Expand Up @@ -254,6 +258,20 @@ fn ci_artifact_consumer_prelude_steps() -> List<Step> {
[ci_heal_revalidation_checkout_step(), ci_heal_revalidation_preflight_step()]
}

fn ci_floor_upstream_red_refusal_step() -> Step {
RunStep {
name: Present { value: "Refuse when an upstream job already established this branch is red (fails fast instead of skipping: a skipped job reports success to the required check)" },
id: none,
run: ci_floor_upstream_red_refusal_script,
shell: none,
env: none,
working_directory: none,
if_condition: none,
continue_on_error: none,
timeout_minutes: Present { value: gunbc_ci_aux_step_timeout_minutes }
}
}

fn ci_floor_job_prelude_steps() -> List<Step> {
[ci_heal_revalidation_checkout_step(), ci_heal_revalidation_preflight_step(), ci_floor_gate_toolchain_step()]
}
Expand Down Expand Up @@ -678,11 +696,11 @@ fn ci_job() -> Job {
id: "ci",
name: none,
runner: gunbc_ci_selected_runner_spec(),
steps: concat(ci_floor_job_prelude_steps(), [ci_release_bins_download_step(), ci_release_bins_unpack_verify_step(), ci_floor_peak_pre_step(), ci_floor_step(), ci_floor_peak_post_step()]),
needs: ["build", "regen"],
steps: concat([ci_floor_upstream_red_refusal_step()], concat(ci_floor_job_prelude_steps(), [ci_release_bins_download_step(), ci_release_bins_unpack_verify_step(), ci_floor_peak_pre_step(), ci_floor_step(), ci_floor_peak_post_step()])),
needs: ["build", "regen", "heal_generated_artifacts"],
env: none,
outputs: none,
if_condition: none,
if_condition: Present { value: ci_floor_runs_unless_cancelled_if },
timeout_minutes: Present { value: gunbc_ci_job_backstop_timeout_minutes() },
continue_on_error: none,
concurrency: none,
Expand Down
6 changes: 6 additions & 0 deletions dag/gunbc/ci_workflow_expressions.dag
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ data ci_workflow_expression_templates: List<ExpressionTemplate> = [

data ci_deploy_push_to_main_if: String = "github.ref == 'refs/heads/main' && github.event_name == 'push'"

data ci_floor_runs_unless_cancelled_if: String = "!cancelled()"

data ci_floor_upstream_red_refusal_script: String = "set -e\nBUILD='${{ needs.build.result }}'\nREGEN='${{ needs.regen.result }}'\nHEAL='${{ needs.heal_generated_artifacts.result }}'\nif [ \"$BUILD\" != \"success\" ] || [ \"$REGEN\" != \"success\" ] || [ \"$HEAL\" = \"failure\" ]; then\necho \"::error::FloorUpstreamAlreadyRed build=$BUILD regen=$REGEN heal=$HEAL — an upstream job already established this branch is red, so the floor refuses before spending its budget. The located cause is that job's own diagnostic; this step adds no verdict of its own.\"\nexit 1\nfi\necho \"[floor-preflight] upstream clean: build=$BUILD regen=$REGEN heal=$HEAL\""

data ci_floor_upstream_red_refusal_note: String = "THE FLOOR REFUSES ON AN ALREADY-RED BRANCH BY RUNNING AND FAILING FAST, NEVER BY BEING SKIPPED. Goal (operator analysis 2026-08-05): 'a branch can already be known to have ci.yml or generated-artifact drift while the 65-minute floor continues consuming a runner'. ci needs [build, regen, heal_generated_artifacts] so the floor is orderable after the cheap preflights, and this step is its first step.\n\nTHE FIRST IMPLEMENTATION OF THIS ROW WAS A FAIL-OPEN AND IS RETRACTED (review 49309, cursor/composer-2.5, 2026-08-06). It set the job condition to `!failure() && !cancelled()`, so an upstream failure SKIPPED ci. The retracted note asserted 'a skipped job does not satisfy a required check, so this makes drift BLOCK the merge'. That sentence was false, and it was written from reasoning rather than from a receipt — the §5 trap of fluent output that was never run. GitHub's own troubleshooting-required-status-checks page states that a job skipped by a conditional REPORTS SUCCESS, and that a job skipped because a needed job failed 'may not block merging'. The live default-branch ruleset (id 16178731) names exactly one required context, `ci`; heal_generated_artifacts is not required. Composing those two facts: drift would have left ci skipped-as-success while only the non-required heal job was red, so GeneratedArtifactDriftGate and the merge-admission receipt stamping could be bypassed entirely. The change intended to remove wasted computation would have removed the only merge wall.\n\nWHAT HOLDS NOW. The condition is `!cancelled()`, so ci RUNS whenever the run is live — including when an upstream job failed — and the required check therefore always produces a real verdict. The refusal moved into this step, which is the job's first step and reads needs.<job>.result directly: build and regen must be `success`, and heal must not be `failure`. A refusal is a typed, located, exit-1 RED on the required context, not an absence. The budget saving is preserved because the step is a runner spin-up plus a string compare, not a floor run.\n\nheal is allowed to be `skipped` because it is pull_request-only (ci_regen_heal_if); on push to main, merge_group and workflow_dispatch it does not run and the floor proceeds, preserving main's cold control. Only `failure` refuses, which covers both HealAuthorCommitRequired (drift the app cannot push) and the deliberate SupersededByHealedHead exit that fires after heal auto-pushes a repair — in the latter case the superseded head correctly stops and the healed commit gets its own run.\n\nA SIDE EFFECT WORTH NAMING: before this change ci carried no condition at all, so a failed build or regen SKIPPED it, and by the same GitHub rule that skip did not reliably block merging either. Making the job unconditional-on-failure closes that pre-existing hole in the same motion rather than leaving a second instance of the class behind."

data ci_concurrency_cancel_in_progress_if: String = "${{ github.event_name != 'merge_group' }}"

data ci_concurrency_event_scoped_stop_policy_note: String = "Event-scoped stop policy (operator ruling 2026-07-22, fail-fast lane): cancel-in-progress is true for pull_request / push / workflow_dispatch (supersede stale runs on new commits) and false for merge_group (the queue must not kill an in-flight check when another PR enters the queue — gunbc.ci_workflow ci_merge_group_trigger_note). The expression is the single authority; dissolve-on: a typed EventStopPolicy row per trigger replaces the hand-spelled string when the workflow model carries event-grain policy by construction."
Expand Down
64 changes: 63 additions & 1 deletion dag/test/claim/ci_heal_job_witness_test.dag
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
module test.claim.ci_heal_job_witness

import v2.std.algebra { list_head, HeadFound, HeadAbsent }
import gunbc.ci_workflow {
ci_workflow,
ci_floor_job_prelude_steps,
ci_regen_job,
ci_heal_generated_artifacts_job,
ci_heal_author_commit_upload_step,
ci_build_job,
Expand Down Expand Up @@ -42,14 +45,18 @@ import gunbc.ci_heal_credential {
ci_heal_branch_ref_expr,
ci_regen_heal_if
}
import gunbc.ci_workflow_expressions {
ci_floor_runs_unless_cancelled_if,
ci_floor_upstream_red_refusal_script
}

import extdeps.github.actions {
Job, Workflow, WorkflowPermissions, PermissionLevel, PermWrite, PermRead,
WorkflowDispatch, Push, PullRequest, Schedule, WorkflowCall, MergeGroup,
Step, UsesStep, RunStep
}

data ci_heal_job_witness_note: String = "Green-by-execution proof that the CI auto-heal job is wired correctly, over the actual Job/Workflow model (not a grep). contents_write_is_scoped_to_heal_only pins least privilege. heal_job_needs_build_not_ci prevents heal from depending on ci. heal_commit_scope_is_auto_push_eligible_only: staging is auto_push_eligible_committed_artifact_paths() only — workflow YAML is AuthorCommitRequired and must never be git add'd. heal_workflow_drift_is_author_commit_required: detects drift, packages repair artifact, emits HealAuthorCommitRequired with regen command, never stages workflow paths. heal_author_commit_upload_step_enrolled: upload-artifact publishes heal-author-commit-required/ when workflow drift exists. heal_author_commit_upload_publishes_its_hidden_payload closes the gap that enrollment alone left open: the step being PRESENT was already witnessed while the artifact it published was empty on every run, because upload-artifact excludes hidden paths by default and this artifact's payload is entirely under .github/. Enrollment is not delivery, and only the second test tells them apart. author_commit_required_paths_all_carry_a_hidden_segment pins the premise the derivation rests on, so the day a non-hidden author-commit path is registered the flag stops being unconditionally true by accident. hidden_segment_predicate_discriminates is the RED control: it fixes both polarities on real corpus paths, so broadening the predicate to match every path, or narrowing it to miss a mid-path dot-directory, reds."
data ci_heal_job_witness_note: String = "Green-by-execution proof that the CI auto-heal job is wired correctly, over the actual Job/Workflow model (not a grep). contents_write_is_scoped_to_heal_only pins least privilege. heal_job_needs_build_not_ci prevents heal from depending on ci. heal_commit_scope_is_auto_push_eligible_only: staging is auto_push_eligible_committed_artifact_paths() only — workflow YAML is AuthorCommitRequired and must never be git add'd. heal_workflow_drift_is_author_commit_required: detects drift, packages repair artifact, emits HealAuthorCommitRequired with regen command, never stages workflow paths. heal_author_commit_upload_step_enrolled: upload-artifact publishes heal-author-commit-required/ when workflow drift exists. heal_author_commit_upload_publishes_its_hidden_payload closes the gap that enrollment alone left open: the step being PRESENT was already witnessed while the artifact it published was empty on every run, because upload-artifact excludes hidden paths by default and this artifact's payload is entirely under .github/. Enrollment is not delivery, and only the second test tells them apart. author_commit_required_paths_all_carry_a_hidden_segment pins the premise the derivation rests on, so the day a non-hidden author-commit path is registered the flag stops being unconditionally true by accident. hidden_segment_predicate_discriminates is the RED control: it fixes both polarities on real corpus paths, so broadening the predicate to match every path, or narrowing it to miss a mid-path dot-directory, reds. THE FOUR FLOOR-PREFLIGHT WITNESSES ARE ONE GROUP AND MUST BE READ TOGETHER. floor_job_waits_on_the_heal_preflight pins the dependency so the expensive floor is ordered after the cheap preflights. floor_job_runs_even_when_an_upstream_job_failed pins the condition as !cancelled() and REFUSES BOTH failure() AND success() BY NAME. floor_refuses_an_already_red_branch_before_its_expensive_steps pins that the refusal lives in the prelude, which ci_job composes ahead of the floor step. floor_refusal_tolerates_a_skipped_heal_but_not_a_failed_one pins the asymmetry between heal and its siblings.\n\nWHY failure() IS REFUSED BY NAME, and it is the opposite of what an earlier revision of this witness asserted: the first cut of this change used `!failure() && !cancelled()`, and a review (review 49309) showed that is a fail-open on the only required merge gate. GitHub's troubleshooting-required-status-checks page states that a job skipped by a conditional REPORTS SUCCESS, and the live default-branch ruleset names exactly one required context, ci. So an upstream failure would have skipped ci, ci would have satisfied its own required check by being skipped, and GeneratedArtifactDriftGate would never have run - drift merging while only the non-required heal job was red. The dangerous edit is therefore no longer 'simplify the condition to success()'; it is 'reintroduce failure() into the condition', which reads as a tightening and is in fact the hole. That is what floor_job_runs_even_when_an_upstream_job_failed exists to red on, and why the refusal moved out of the job condition and into an executing first step that exits 1. heal_commit_scope_is_auto_push_eligible_only: staging is auto_push_eligible_committed_artifact_paths() only — workflow YAML is AuthorCommitRequired and must never be git add'd. heal_workflow_drift_is_author_commit_required: detects drift, packages repair artifact, emits HealAuthorCommitRequired with regen command, never stages workflow paths. heal_author_commit_upload_step_enrolled: upload-artifact publishes heal-author-commit-required/ when workflow drift exists."

fn perm_is_write(p: PermissionLevel) -> Bool {
match p { PermWrite => true _ => false }
Expand Down Expand Up @@ -118,6 +125,61 @@ test fn heal_job_needs_build_not_ci() -> Bool {
ci_heal_generated_artifacts_job().needs == ["build"]
}

test fn floor_job_waits_on_the_heal_preflight() -> Bool {
any(ci_job().needs, n => n == "heal_generated_artifacts")
&& any(ci_job().needs, n => n == "build")
&& any(ci_job().needs, n => n == "regen")
}

test fn floor_job_runs_even_when_an_upstream_job_failed() -> Bool {
match ci_job().if_condition {
Present { value: s } =>
(s == ci_floor_runs_unless_cancelled_if)
&& string_contains(s: s, pattern: "!cancelled()")
&& !string_contains(s: s, pattern: "failure()")
&& !string_contains(s: s, pattern: "success()")
Absent => false
}
}

fn step_is_the_upstream_red_refusal(s: Step) -> Bool {
match s {
RunStep { name: _, id: _, run: r, shell: _, env: _, working_directory: _, if_condition: _, continue_on_error: _, timeout_minutes: _ } =>
r == ci_floor_upstream_red_refusal_script
UsesStep { name: _, id: _, uses: _, with: _, env: _, if_condition: _, continue_on_error: _, timeout_minutes: _ } => false
}
}

test fn floor_refusal_is_exactly_the_first_step() -> Bool {
match list_head(xs: ci_job().steps) {
HeadFound { value: head } => step_is_the_upstream_red_refusal(s: head)
HeadAbsent => false
}
}

test fn floor_refuses_an_already_red_branch_before_its_expensive_steps() -> Bool {
fold(ci_job().steps, init: false, f: (found, s) => found || step_is_the_upstream_red_refusal(s: s))
&& string_contains(s: ci_floor_upstream_red_refusal_script, pattern: "needs.build.result")
&& string_contains(s: ci_floor_upstream_red_refusal_script, pattern: "needs.regen.result")
&& string_contains(s: ci_floor_upstream_red_refusal_script, pattern: "needs.heal_generated_artifacts.result")
&& string_contains(s: ci_floor_upstream_red_refusal_script, pattern: "FloorUpstreamAlreadyRed")
&& string_contains(s: ci_floor_upstream_red_refusal_script, pattern: "exit 1")
}

test fn floor_refusal_reaches_only_the_job_whose_needs_it_names() -> Bool {
!fold(ci_regen_job().steps, init: false, f: (found, s) => found || step_is_the_upstream_red_refusal(s: s))
&& !fold(ci_build_job().steps, init: false, f: (found, s) => found || step_is_the_upstream_red_refusal(s: s))
&& !fold(ci_heal_generated_artifacts_job().steps, init: false, f: (found, s) => found || step_is_the_upstream_red_refusal(s: s))
&& !fold(ci_floor_job_prelude_steps(), init: false, f: (found, s) => found || step_is_the_upstream_red_refusal(s: s))
}

test fn floor_refusal_tolerates_a_skipped_heal_but_not_a_failed_one() -> Bool {
string_contains(s: ci_floor_upstream_red_refusal_script, pattern: "\"$HEAL\" = \"failure\"")
&& !string_contains(s: ci_floor_upstream_red_refusal_script, pattern: "\"$HEAL\" != \"success\"")
&& string_contains(s: ci_floor_upstream_red_refusal_script, pattern: "\"$BUILD\" != \"success\"")
&& string_contains(s: ci_floor_upstream_red_refusal_script, pattern: "\"$REGEN\" != \"success\"")
}

test fn heal_job_runs_on_pr_only() -> Bool {
match ci_heal_generated_artifacts_job().if_condition {
Present { value: s } =>
Expand Down
Loading