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: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ permissions:
contents: read
pull-requests: read

concurrency:
# Trusted PR copies share a group across push/manual events. Manual source
# branches replace their own runs. Unique lifecycle/tag groups preserve
# every validation and protected staging run.
group: >-
full-ci-${{ (startsWith(github.ref, 'refs/heads/pull-request/') ||
(github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/heads/') &&
github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/')))
&& github.ref || github.run_id }}
cancel-in-progress: true

jobs:
verify-target:
name: Verify Exact Target
Expand Down
30 changes: 30 additions & 0 deletions docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ for pinned scheduler settings, measurement selection, and provenance.
| **Require Fast CI** failed or timed out | Open the linked/latest standalone Fast run for the same branch and SHA; resolve its failure or dispatch Fast CI first, then rerun Full CI |
| `Fast CI Success` failed with missing or skipped substantive jobs | Inspect the required job results and cancellation history; draft status and labels do not skip Fast CI |
| Full CI job skipped | Read **Select Full CI Scope** and the aggregate summary; only explicit N/A is acceptable |
| Full CI canceled after another PR run starts | A newer run replaced validation in the same PR/branch concurrency group; inspect the replacement run's SHA and results |
| `Full CI Success` green, workflow still `waiting` | Validation finished; main/release wheel staging may be waiting for `automated-release` approval |
| New nightly pending, earlier nightly waiting | Nightly's single concurrency group includes protected staging; an unapproved run can hold later validation behind it |
| Prediction Regression green with reported drift | Working-case regression checks passed; review the numerical changes in the report |
Expand Down Expand Up @@ -567,6 +568,35 @@ gh workflow run ci.yml --repo ai-dynamo/aisimulate \
--ref "${ci_branch}" -f expected_sha="${ci_sha}"
```

Manual runs provide diagnostic validation. GitHub does not count job checks
from `workflow_dispatch` toward required PR status checks, even when the run
uses the current PR SHA. See [GitHub's required-check troubleshooting guide](https://docs.github.com/en/pull-requests/how-tos/merge-and-close-pull-requests/troubleshooting-required-status-checks#checks-from-some-workflow-jobs-are-not-evaluated).
For PR merge-gate validation, a maintainer admits the reviewed current head
through copy-pr-bot; the resulting trusted-copy **push** launches eligible
Fast and Full CI runs:

```bash
ci_pr=123 # Replace with the reviewed PR to validate.
ci_sha="$(gh pr view "${ci_pr}" --repo ai-dynamo/aisimulate --json headRefOid --jq .headRefOid)"
gh pr comment "${ci_pr}" --repo ai-dynamo/aisimulate \
--body "/ok to test ${ci_sha}"
```

Full CI cancels older queued and running validation for the same trusted
`pull-request/N` copy. Manual dispatches on that copy share the push run's
concurrency group: they can cancel an eligible push run without satisfying its
required check. Preserve the push-triggered validation for the current PR
head. If it fails transiently, retry its failed jobs with `gh run rerun RUN_ID
--failed` after confirming that the run still targets the current head.

Manual source-branch runs replace only runs on the same branch. Different PRs
remain independent. Main, `release/*`, and tag runs use unique groups, so later
runs cannot cancel their validation or protected staging. Concurrency only
applies to runs using the updated workflow; existing runs and older branches
are not retroactively covered. A replacement still needs successful eligible
checks for its exact SHA. Re-running an old revision can replace a newer run
in the same group: always verify the current PR head before retrying validation.

Standalone Fast CI accepts these manual inputs:

| Input | Behavior |
Expand Down
41 changes: 41 additions & 0 deletions tests/test_ci_workflow_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,47 @@ def _run_comparison_base(
return result, output


def _full_ci_concurrency_group(event: str, ref: str, run_id: str) -> str:
configuration = _workflow("ci.yml")["concurrency"]
assert configuration["cancel-in-progress"] == "true"
prefix, expression = configuration["group"].split("${{", 1)
expression = expression.rsplit("}}", 1)[0]
values = {
"github.event_name": event,
"github.ref": ref,
"github.run_id": run_id,
}
expression = re.sub(r"github\.[\w_]+", lambda match: repr(values[match[0]]), expression)
expression = expression.replace("&&", " and ").replace("||", " or ").replace("!startsWith", "not startsWith")
return prefix + str(
eval(
" ".join(expression.splitlines()),
{"__builtins__": {}},
{"startsWith": str.startswith},
)
)


def test_full_ci_replaces_same_pr_across_trusted_and_manual_runs():
group = _full_ci_concurrency_group("push", "refs/heads/pull-request/159", "101")
assert group == _full_ci_concurrency_group("push", "refs/heads/pull-request/159", "102")
assert group == _full_ci_concurrency_group("workflow_dispatch", "refs/heads/pull-request/159", "103")
assert group != _full_ci_concurrency_group("push", "refs/heads/pull-request/160", "105")


def test_full_ci_manual_source_branches_replace_only_their_branch():
group = _full_ci_concurrency_group("workflow_dispatch", "refs/heads/feature", "101")
assert group == _full_ci_concurrency_group("workflow_dispatch", "refs/heads/feature", "102")
assert group != _full_ci_concurrency_group("workflow_dispatch", "refs/heads/other-feature", "103")
assert group != _full_ci_concurrency_group("push", "refs/heads/pull-request/159", "104")


@pytest.mark.parametrize("event", ["push", "workflow_dispatch"])
@pytest.mark.parametrize("ref", ["refs/heads/main", "refs/heads/release/0.12.0", "refs/tags/v0.12.0"])
def test_full_ci_preserves_every_lifecycle_and_tag_run(event, ref):
assert _full_ci_concurrency_group(event, ref, "101") != _full_ci_concurrency_group(event, ref, "102")


def test_fast_ci_is_standalone_with_an_exact_commit_prerequisite() -> None:
fast_ci = _workflow("fast-ci.yml")
assert "workflow_call" not in fast_ci["on"]
Expand Down
Loading