Skip to content
Open
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
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ name: CI

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]

permissions:
contents: read

concurrency:
group: wardnet-ci-${{ github.repository }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('run-{0}', github.run_id) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# Only opened/synchronize validation shares the PR group. State-only
# re-entry uses an independent run group so it cannot cancel an unchanged
# current head; synchronize may cancel only a genuinely superseded head.
group: wardnet-ci-${{ github.repository }}-${{ github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize') && format('pr-{0}', github.event.pull_request.number) || format('run-{0}', github.run_id) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' && github.event.action == 'synchronize' }}

jobs:
rust:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
# Bounded smoke fuzzing on every PR that touches fuzzable surfaces or the
# harness. Time budget is capped low to keep CI cost predictable.
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- "src/**"
- "crates/**"
Expand All @@ -18,12 +19,16 @@ permissions:
contents: read

concurrency:
group: wardnet-fuzz-${{ github.repository }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.event_name == 'schedule' && format('schedule-{0}', github.ref) || format('run-{0}', github.run_id) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# Keep a fixed workflow identity. Only opened/synchronize validation shares
# the PR group; state-only re-entry is isolated so an unchanged head is not
# cancelled. A synchronize event may cancel only the superseded PR head.
group: wardnet-fuzz-${{ github.repository }}-${{ github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize') && format('pr-{0}', github.event.pull_request.number) || format('run-{0}', github.run_id) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' && github.event.action == 'synchronize' }}

jobs:
fuzz:
# One runner covers every bounded target; a matrix would consume four org slots.
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
runs-on: ubuntu-24.04
env:
FUZZ_SECONDS: ${{ github.event_name == 'pull_request' && '60' || '300' }}
Expand Down
27 changes: 27 additions & 0 deletions docs/doctoring/ci-queue-concurrency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# CI queue concurrency evidence

Wardnet's repository-owned `CI` and `Fuzz` workflows coalesce only **superseded pull-request heads**. `opened` and `synchronize` validation share a fixed, workflow-specific PR concurrency group; only a `synchronize` event may cancel work in that group. `reopened` and `ready_for_review` use run-specific groups so a state-only transition cannot discard validation for an unchanged head. `converted_to_draft` and `closed` are deliberately not cancellation triggers.

Draft work is still prevented from consuming a local Rust/fuzz runner when the event itself observes a Draft PR: the job-level guard skips `opened`, `synchronize`, `reopened`, or `ready_for_review` events whose current PR state is Draft. A later Draft `synchronize` event may cancel an older PR-group run because the commit has genuinely superseded that older head, then skips its own job. Merely converting an unchanged current head to Draft does not cancel that head's already queued/running evidence. This preserves the repository invariant that only superseded heads may be cancelled.

`Fuzz` remains path-filtered to Wardnet's fuzzable surfaces and harness. Because Draft/closed state transitions are no longer used as cancellation signals, that path filter cannot suppress a required state-transition cancellation event. The fixed `wardnet-ci` and `wardnet-fuzz` prefixes also prevent a PR from changing a workflow display name to escape or collide with another workflow's concurrency group.

This is a queue-pressure and evidence-integrity policy, not a substitute for required security or review gates. A cancelled, skipped, queued, or stale predecessor run is never promoted to success for a newer head. The workflow must still obtain terminal evidence on the exact current head before merge.

## Research basis

Juloori et al. (2025) study large-scale continuous-integration queue scheduling at Uber. Their SubmitQueue measurements show that unnecessary or prematurely aborted speculative builds can materially increase resource consumption and waiting time; the paper reports that prioritizing likely-needed builds and pruning low-value speculation reduced CI resource use and p95 waiting time in the evaluated monorepos. Wardnet does **not** copy SubmitQueue's probabilistic scheduler. The relevant design inference is narrower: when a newer commit makes an older PR execution non-authoritative, retaining both executions consumes scarce CI capacity without improving exact-head evidence. A PR state transition alone is not treated as supersession.

GitHub Actions' concurrency contract provides the platform mechanism for that bounded inference: jobs or workflow runs sharing a concurrency group may be cancelled when a newer run in the group is enqueued and `cancel-in-progress` is enabled. Wardnet therefore uses immutable workflow-specific prefixes, repository identity, and the PR number for the opened/synchronize validation lineage; state-only and non-PR executions fall back to `run_id` so unrelated or unchanged-head evidence lanes are not coalesced.

## Traceability and licensing

The research paper is published in the Proceedings of the 47th International Conference on Software Engineering (ICSE 2025), and the authors' arXiv version is licensed CC BY 4.0, which permits redistribution with attribution. The redistribution-permitted full-text PDF is required in Wardnet's evidence pack at `docs/papers/ci-at-scale-lean-green-fast-arxiv-2501.03440.pdf` before this process-change candidate is merge-ready; citation-only evidence does not satisfy that packaging gate.
Comment thread
seonghobae marked this conversation as resolved.

### References

GitHub. (n.d.). *Control the concurrency of workflows and jobs*. GitHub Docs. https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency

Juloori, D., Lin, Z., Williams, M., Shin, E., & Mahajan, S. (2025). CI at scale: Lean, green, and fast. *Proceedings of the 47th International Conference on Software Engineering*. https://doi.org/10.48550/arXiv.2501.03440

ArXiv full text and license: https://arxiv.org/abs/2501.03440 ; https://creativecommons.org/licenses/by/4.0/
Binary file not shown.
43 changes: 35 additions & 8 deletions tests/workflow_queue_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,42 @@ fn workflow_text(name: &str) -> String {

#[test]
fn local_pr_workflows_cancel_only_superseded_heads_of_the_same_pull_request() {
for (name, fixed_group) in [("ci.yml", "wardnet-ci"), ("fuzz.yml", "wardnet-fuzz")] {
for (name, expected_group) in [
(
"ci.yml",
"group: wardnet-ci-${{ github.repository }}-${{ github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize') && format('pr-{0}', github.event.pull_request.number) || format('run-{0}', github.run_id) }}",
),
(
"fuzz.yml",
"group: wardnet-fuzz-${{ github.repository }}-${{ github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize') && format('pr-{0}', github.event.pull_request.number) || format('run-{0}', github.run_id) }}",
),
] {
let workflow = workflow_text(name);
assert!(workflow.contains(&format!(
"group: {fixed_group}-${{{{ github.repository }}}}-${{{{ github.event_name == 'pull_request'"
)));
assert!(workflow.contains("format('pr-{0}', github.event.pull_request.number)"));
assert!(
workflow.contains("cancel-in-progress: ${{ github.event_name == 'pull_request' }}")
);
assert!(workflow.contains(expected_group));
assert!(!workflow.contains("group: ${{ github.workflow }}"));
assert!(workflow.contains(
"cancel-in-progress: ${{ github.event_name == 'pull_request' && github.event.action == 'synchronize' }}"
));
assert!(workflow.contains("types: [opened, synchronize, reopened, ready_for_review]"));
assert!(!workflow.contains("converted_to_draft"));
assert!(!workflow.contains("closed]"));
assert!(workflow.contains(
"if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}"
));
}
}

#[test]
fn fuzz_keeps_path_filtered_validation_without_state_transition_cancellation() {
let workflow = workflow_text("fuzz.yml");
assert!(workflow.contains(" paths:\n"));
for path in [
"src/**",
"crates/**",
"fuzz/**",
".github/workflows/fuzz.yml",
] {
assert!(workflow.contains(path));
}
}

Expand Down
Loading