Skip to content
15 changes: 14 additions & 1 deletion dsl/gunbc/ci_failure_class.dag
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module gunbc.ci_failure_class

import std.process { ProcessExit, ExitSuccess, ExitFailure }
import std.types { ContentHash }
import std.types { ContentHash, String }
import std.disposition { Disposition, Scaffold, SingleAuthority }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import extdeps.runtime.errno { PosixErrno, EAGAIN, errno_strerror }
import extdeps.rust.std_process { StdProcessSpawnOutcome, SpawnFailed, spawn_outcome_log_fragment }
import extdeps.cache.sccache {
Expand Down Expand Up @@ -146,6 +148,17 @@ type MergeGatingStatus

data merge_freshness_gating_status: MergeGatingStatus = GatingComputedDeferred

data merge_admission_gating_activation_trigger: String = "flip merge_freshness_gating_status from GatingComputedDeferred to GatingEnforced after real CI run proves admit-real-fresh AND block-stale/red"

data merge_admission_enforcement_gating_scaffold: Disposition = Scaffold {
dissolves_to: SingleAuthority,
bind: DeclarationRef {
module_path: "gunbc.ci_failure_class",
decl_name: "merge_freshness_gating_status",
field: WholeDeclaration
}
}

fn gating_is_enforced(s: MergeGatingStatus) -> Bool {
match s {
GatingComputedDeferred => false
Expand Down
6 changes: 5 additions & 1 deletion dsl/gunbc/ci_spec.dag
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import tools.build_step { BuildArtifact }
import extdeps.languages.bash.program { Concat, lit, var_ref }
import gunbc.ci_failure_class { infra_retry_grep_alternation }
import gunbc.ci_deploy_access { RunnerHostAccess, ci_deploy_srv1_runner_access }
import gunbc.merge_admission_produce { ci_floor_stamp_merge_admission_script }

type DiffMode = DiffMergeBase | DiffTwoDot

Expand Down Expand Up @@ -222,7 +223,10 @@ fn gunbc_ci_run_script(spec: CiSpec) -> String {
concat(ci_release_build_script(), "\n"),
concat(git_fetch_script(policy: spec.diff_policy), "\n")
),
scheduler_invoke(spec: spec)
concat(
concat(scheduler_invoke(spec: spec), "\n"),
ci_floor_stamp_merge_admission_script()
)
)
}

Expand Down
32 changes: 31 additions & 1 deletion dsl/gunbc/ci_workflow.dag
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import gunbc.ci_spec {
CiSpec, gunbc_ci_spec, gunbc_ci_workflow_run, gunbc_ci_rust_job_run,
gunbc_ci_deploy_stages, DeployStage, gunbc_ci_deploy_run_script,
}
import gunbc.merge_admission_produce {
ci_merge_admission_gate_script,
}
import gunbc.ci_workflow_expressions { ci_deploy_push_to_main_if }
import gunbc.commit_workflow { commit_gate_roster, project_ci_floor_gates }
import gunbc.runner_spec_from_offer { gunbc_ci_runner_spec }
import std.disposition { Disposition, Scaffold, SingleAuthority }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import extdeps.github.actions {
Workflow, Job, Step, RunStep, UsesStep,
WorkflowTrigger, Push, PullRequest, WorkflowDispatch,
Expand Down Expand Up @@ -161,6 +166,31 @@ fn ci_floor_step() -> Step {
}
}

data merge_admission_gate_step_name: String = "Merge-admission gate (NOT ENFORCING until GatingEnforced)"

data merge_admission_ci_gate_step_scaffold: Disposition = Scaffold {
dissolves_to: SingleAuthority,
bind: DeclarationRef {
module_path: "gunbc.ci_workflow",
decl_name: "ci_merge_admission_gate_step",
field: WholeDeclaration
}
}

fn ci_merge_admission_gate_step() -> Step {
RunStep {
name: Present { value: merge_admission_gate_step_name },
id: none,
run: ci_merge_admission_gate_script(),
shell: none,
env: none,
working_directory: none,
if_condition: none,
continue_on_error: none,
timeout_minutes: none
}
}

fn ci_nextest_install_step() -> Step {
RunStep {
name: Present { value: "Install cargo-nextest" },
Expand Down Expand Up @@ -264,7 +294,7 @@ fn ci_job() -> Job {
id: "ci",
name: none,
runner: gunbc_ci_runner_spec(),
steps: concat(ci_prelude_steps(), [ci_floor_step()]),
steps: concat(ci_prelude_steps(), [ci_floor_step(), ci_merge_admission_gate_step()]),
needs: [],
env: none,
outputs: none,
Expand Down
16 changes: 16 additions & 0 deletions dsl/gunbc/merge_admission.dag
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ fn gate_roster_content_hash(roster: List<CommitCheckEnrollment>) -> ContentHash

data current_gate_roster_hash: ContentHash = gate_roster_content_hash(roster: commit_gate_roster)

fn mint_merge_admission_receipt(
pr_number: Int,
head_sha: CommitSha,
base_tree_hash: ContentHash,
gate_roster_hash: ContentHash,
conclusion: CheckConclusion,
) -> MergeAdmissionReceipt {
MergeAdmissionReceipt {
pr_number: pr_number,
head_sha: head_sha,
base_tree_hash: base_tree_hash,
gate_roster_hash: gate_roster_hash,
conclusion: conclusion,
}
}

type MergeAdmissionVerdict
= MergeAdmitted
| MergeDeniedStaleBase
Expand Down
152 changes: 152 additions & 0 deletions dsl/gunbc/merge_admission_produce.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
module gunbc.merge_admission_produce

import std.types { ContentHash, CommitSha, String, Int, List, Bool }
import extdeps.github.checks {
CheckConclusion,
check_conclusion_wire_label,
parse_check_conclusion,
Success,
Failure,
}
import gunbc.merge_admission {
MergeAdmissionReceipt,
mint_merge_admission_receipt,
current_gate_roster_hash,
}
import gunbc.ci_layer_roots { witness_layer_roots, witness_layer_source_flags }

data merge_admission_merge_base_ref: String = "origin/main"

data merge_admission_receipt_schema: String = "gunbc.merge_admission_receipt.v1"

data merge_admission_receipt_rel_path: String = ".gunbc/merge-admission-receipt.wire"

fn merge_admission_receipt_path(root: String) -> String {
concat(concat(root, "/"), merge_admission_receipt_rel_path)
}

fn render_receipt_wire_with_pr_line(receipt: MergeAdmissionReceipt, pr_line: String) -> String {
concat(
concat(
concat(
concat(receipt.base_tree_hash, "\n"),
concat(receipt.gate_roster_hash, "\n")
),
concat(check_conclusion_wire_label(c: receipt.conclusion), "\n")
),
concat(concat(receipt.head_sha, "\n"), pr_line)
)
}

fn render_receipt_wire(receipt: MergeAdmissionReceipt) -> String {
render_receipt_wire_with_pr_line(receipt: receipt, pr_line: to_string(receipt.pr_number))
}

fn receipt_wire_lines(text: String) -> List<String> {
split(s: text, delimiter: "\n")
}

fn parse_receipt_wire(text: String) -> MergeAdmissionReceipt? {
let lines = receipt_wire_lines(text: text)
if length(lines) < 3 {
none
} else {
match parse_check_conclusion(raw: lines.skip(n: 2).first()) {
Present { value: conclusion } =>
Present {
value: MergeAdmissionReceipt {
pr_number: 0,
head_sha: "0000000000000000000000000000000000000000",
base_tree_hash: lines.first(),
gate_roster_hash: lines.skip(n: 1).first(),
conclusion: conclusion,
}
}
Absent => none
}
}
}

fn ci_repo_root_shell() -> String {
"$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
}

fn gunbc_run_bin_shell() -> String {
"\"$ROOT/target/release/gunbc\" run"
}

fn merge_admission_stamp_invoke() -> String {
concat(
concat(
concat(gunbc_run_bin_shell(), witness_layer_source_flags(roots: witness_layer_roots)),
" --entry dsl/tools/merge_admission_stamp.dag --function main"
),
"\n"
)
}

fn merge_admission_gate_invoke() -> String {
concat(
concat(
concat(gunbc_run_bin_shell(), witness_layer_source_flags(roots: witness_layer_roots)),
" --entry dsl/tools/merge_admission_gate.dag --function main"
),
"\n"
)
}

fn ci_merge_admission_stamp_script() -> String {
concat(
concat("ROOT=", ci_repo_root_shell(), "\n"),
merge_admission_stamp_invoke()
)
}

fn ci_merge_admission_gate_script() -> String {
concat(
concat(
concat("ROOT=", ci_repo_root_shell(), "\n"),
concat(git_fetch_script_for_gate(), "\n")
),
merge_admission_gate_invoke()
)
}

fn git_fetch_script_for_gate() -> String {
let parts = split(s: merge_admission_merge_base_ref, delimiter: "/")
let remote = parts.first()
let ref_name = fold(parts.skip(n: 1), init: "", f: (acc, p) => if acc == "" { p } else { concat(concat(acc, "/"), p) })
concat(
concat(concat("git fetch --no-tags ", remote), " "),
concat(ref_name, " 2>/dev/null || true")
)
}

fn floor_exit_to_check_conclusion(exit_code: Int) -> CheckConclusion {
if exit_code == 0 { Success } else { Failure }
}

fn stamp_floor_receipt(
pr_number: Int,
head_sha: CommitSha,
base_tree_hash: ContentHash,
floor_exit_code: Int,
) -> MergeAdmissionReceipt {
mint_merge_admission_receipt(
pr_number: pr_number,
head_sha: head_sha,
base_tree_hash: base_tree_hash,
gate_roster_hash: current_gate_roster_hash,
conclusion: floor_exit_to_check_conclusion(exit_code: floor_exit_code),
)
}

fn ci_floor_stamp_merge_admission_script() -> String {
concat(
concat(
"FLOOR_EXIT=$?\n",
concat("CI_FLOOR_EXIT=$FLOOR_EXIT ", ci_merge_admission_stamp_script())
),
"exit $FLOOR_EXIT\n"
)
}
Loading
Loading