Skip to content

EDGEAI-1554: Re-pin shared CI, and run advisories ungated - #194

Merged
sebastient merged 3 commits into
mainfrom
chore/EDGEAI-1554-repin-advisories
Sep 16, 2026
Merged

sebastient merged 3 commits into
mainfrom
chore/EDGEAI-1554-repin-advisories

Conversation

@sebastient

@sebastient sebastient commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Picks up EdgeFirstAI/.github#26a9b62d89797eea74 — plus two fixes and a restructure.

1. The nightly gate now actually gates

It applied its "has main moved" check only on schedule, so a workflow_dispatch bypassed it silently. It also compared against the last successful run, and hal's last green nightly on main was 2025-12-08 — so every comparison since has been against a December commit and the gate has never once skipped.

Now: every trigger, compared against the last run that reached a verdict (success or failure; cancelled and friends are discarded, since a run killed before its lanes start proves nothing). A force input is forwarded for deliberately re-running an unchanged commit. The || false fallback is load-bearing — inputs is null on a schedule trigger.

2. cargo audit runs ungated

It moved out of rust-full into advisories.yml and is called without needs: changed. Every other lane asks a question about the code; this one asks about the RustSec database, which moves whether the code does or not. The 2026-09-15 nightly failed on RUSTSEC-2026-0204 against a Cargo.lock nobody had touched — a gated lane would have gone quiet exactly then.

Free hard-coded runner, parses Cargo.lock without building. With #193 merged this should now be green.

nightly-extra is also gone for hal: with audit removed it is only the hack lane, which skips when hack-args is empty. It used to boot a runner and install two tools to run the audit alone.

3. fetch-angle.sh — the macOS failure

Root cause, and it was never about the release asset:

mv "${DEST}"/dist/* "${DEST}"/ 2>/dev/null || true

When ${DEST}/EGL.xcframework already exists — which a partially restored target/ cache leaves behind — mv refuses to merge one directory into another and fails. The redirect hid the message, || true hid the status, so the payload stayed in ${DEST}/dist and the script ran on to fail in the cp with "No such file or directory" for a path the zip definitely contains.

That is why the same commit passed at 21:12 and failed at 00:57: the first had a cache hit and skipped the flatten entirely. Windows and iOS use the same script and the same release and were never affected.

Now: extract to a staging dir and move each item in deliberately, replacing what is there; verify both framework binaries afterwards and fail naming the missing path; require both binaries for the "already extracted" short-circuit, since checking only EGL let a half-populated directory look complete; drop a stale flat-lib on re-extract.

Reproduced against the real v2.1.28252 package — the old logic fails with the exact CI error and leaves dist/ behind; the new logic passes from a partial cache, a clean directory, a warm cache and a corrupted one.

4. Mutation testing moves to its own workflow

The corpus is ~7057 mutants, roughly 39 hours of work:

crate mutants est.
crates/decoder 3607 ~20.0 h
crates/tensor 3237 ~18.0 h
crates/tracker 213 ~1.2 h

As a nightly job it capped at 120 minutes, got through 5%, never once completed, and held the run open for two hours after every other lane finished — which also blocked log retrieval for lanes that had already failed. Everything else in the nightly finishes in 27 minutes.

mutants.yml now tests a rotating window of shards each night on its own schedule. Parallelism is a matrix of runners, not cargo-mutants' --jobs — that is documented as incompatible with --in-place, since parallel jobs on one machine each need their own tree and target directory at "often 2GB+", which does not fit one runner's ~14GB disk. A matrix gives every shard a whole machine, so --in-place stays valid.

Free standard runners, and no coverage instrumentation — by design, not omission. A mutant is caught when a test fails, so which lines ran is irrelevant.

A bug found while verifying the rotation

Walking the window by date looked obvious, but snapping to window-aligned blocks strands the tail whenever the window does not divide the shard count. At 64 shards with a window of 10, integer division gives 6 blocks covering 0..59 and shards 60-63 are never tested at all — silently.

Fixed by walking the start one window per night modulo the total. Verified by extracting the plan step verbatim from the YAML and simulating consecutive nights:

total=64 window=8    full coverage after 8 nights   <- the default, exact sweep
total=64 window=10   full coverage after 7 nights
total=64 window=12   full coverage after 6 nights
total=10 window=3    full coverage after 4 nights

Not yet verified

workflow_dispatch is unavailable for a workflow that is not on the default branch, so mutants.yml cannot be test-run until this merges — its first real execution will be the first scheduled one. The shard arithmetic is verified; the cargo mutants --shard invocation and per-shard timing on a free runner are not.

ci:full is on this PR so the macOS lanes actually exercise the fetch-angle.sh fix; Quick does not run macOS.

Copilot AI balanced review requested due to automatic review settings September 16, 2026 00:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The nightly gate may miss the last verdict, and related documentation still describes the previous behavior.

Pull request overview

This PR re-pins shared CI workflows and updates Nightly so advisory scans run independently of code-change gating.

Changes:

  • Re-pins shared workflows to 797eea74.
  • Adds nightly force support and ungated advisory scanning.
  • Removes the duplicated workflow pin from documentation.
File summaries
File Summary
.github/workflows/tag-release.yml Re-pins the shared release workflow.
.github/workflows/sbom.yml Re-pins the shared SBOM workflow.
.github/workflows/README.md Removes the stale duplicated SHA.
.github/workflows/nightly.yml Updates gating, force forwarding, advisory scanning, and hack-lane behavior. The 30-run lookup may miss an earlier verdict (moderate, 1 vote); related documentation is also stale (nit, 1 vote).
.github/workflows/ci.yml Re-pins Quick and Full CI workflows.
Review details

Suppressed comments (2)

.github/workflows/nightly.yml:27

  • The pinned nightly-gate only requests 30 completed runs and then filters out cancelled/non-verdict conclusions locally. If repeated manual runs are cancelled before a verdict, the last success/failure can fall beyond that page, leaving last empty and causing an unchanged main to run the board again. Please paginate until a success/failure is found (or apply an equivalent server-side filter) so this gate reliably honors its last-verdict contract.
    uses: EdgeFirstAI/.github/.github/workflows/nightly-gate.yml@797eea745ac3c9896c6b3756251cda0530d6aa27

.github/workflows/nightly.yml:42

  • This changes the Nightly contract, but .github/workflows/README.md:15 and CONTRIBUTING.md:368 still say Nightly runs only when main moves and includes cargo hack. With advisories intentionally ungated and hack-args: "" disabling the hack lane, please update those descriptions so the documented schedule and lane list match the workflow.
  advisories:
    uses: EdgeFirstAI/.github/.github/workflows/advisories.yml@797eea745ac3c9896c6b3756251cda0530d6aa27
  • Files reviewed: 5/5 changed files
  • Comments generated: 0
  • Review effort level: Lite (auto)

Note

Copilot is running an experiment and ran this review at Lite.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Picks up EdgeFirstAI/.github#26. Two caller-visible changes come with it.

The nightly gate now applies on every trigger and compares against the
last run that reached a verdict, so a workflow_dispatch no longer
bypasses it silently. Forward a `force` input for deliberately re-running
an unchanged commit; `inputs` is null on the schedule trigger, so the
`|| false` fallback is required or the gate's boolean input rejects an
empty string.

cargo audit moved out of rust-full into advisories.yml, so it has to be
called explicitly, and it is called without `needs: changed`. Every other
lane here asks a question about the code and loses nothing by skipping an
unchanged commit; cargo audit asks a question about the RustSec database,
which changes daily whether the code does or not. The 2026-09-15 nightly
failed on RUSTSEC-2026-0204 against a Cargo.lock nobody had touched,
which a gated lane would have missed.

Drop the shared SHA from the workflows README. It was a second copy of
the pin that Dependabot does not update, and it was stale exactly one
re-pin after the paragraph claiming the SHA appears only in `uses:`.

Signed-off-by: Sébastien Taylor <[email protected]>
fetch-angle.sh extracted into ${DEST} and flattened the zip's top level
with `mv "${DEST}"/dist/* "${DEST}"/ 2>/dev/null || true`. When
${DEST}/EGL.xcframework already existed -- which a partially restored
target/ cache leaves behind -- mv refuses to merge one directory into
another and fails. The redirect hid the message and `|| true` hid the
status, so the payload stayed in ${DEST}/dist and the script ran on to
fail in the cp, reporting "No such file or directory" for a path the zip
definitely contains.

That is why the same commit passed at 21:12 and failed at 00:57: the
first had a cache hit and skipped the flatten entirely. The Windows and
iOS lanes were never affected, which is why only macOS failed.

Extract to a staging dir and move each payload item in deliberately,
replacing what is there. Verify both framework binaries afterwards and
fail naming the missing path and the directory contents, instead of
letting a later cp produce a misleading error. Require both binaries for
the "already extracted" short-circuit, since checking only EGL let a
half-populated directory look complete, and drop a stale flat-lib when
re-extracting so it cannot be reused against binaries it did not come
from.

Reproduced against the real v2.1.28252 package: the old logic fails with
the exact CI error and leaves dist/ behind; the new logic passes from a
partial cache, a clean directory, a warm cache and a corrupted one.

Signed-off-by: Sébastien Taylor <[email protected]>
@sebastient
sebastient force-pushed the chore/EDGEAI-1554-repin-advisories branch from eaa14de to 5c3beb1 Compare September 16, 2026 01:39
The corpus is ~7057 mutants and roughly 39 hours of work, so it cannot
finish in one sitting. As a nightly job it capped at 120 minutes, got
through 5%, never once completed, and held the whole run open for two
hours after every other lane was done -- which also blocked log retrieval
for lanes that had already failed. Everything else in the nightly
finishes in 27 minutes.

Give it its own workflow and its own schedule, testing a rotating window
of shards each night. cargo-mutants' --shard k/n is deterministic, so the
sweep works through the corpus and rolls over.

Parallelism is a matrix of runners rather than cargo-mutants' --jobs.
--jobs is documented as incompatible with --in-place, because parallel
jobs on one machine each need a copy of the tree and a target directory
of "often 2GB+", which does not fit a single runner's ~14GB disk. A
matrix gives every shard a whole machine, so --in-place stays valid and
each runner keeps one target directory.

Free standard runners: nothing waits on this, so speed buys nothing worth
billing for, and the same hour on a larger class would be roughly $150 a
month. No coverage instrumentation either, by design rather than
omission -- a mutant is caught when a test fails, so which lines ran is
irrelevant, and profiling every rebuilt binary is pure overhead.

The rotation is walked one window per night modulo the shard count rather
than snapped to window-aligned blocks. Snapping strands the tail whenever
the window does not divide the total: at 64 shards and a window of 10,
integer division yields 6 blocks covering 0..59 and shards 60-63 are
never tested at all. Verified by extracting the plan step verbatim and
simulating consecutive nights: the default 64/8 sweeps the corpus in
exactly 8 nights and rolls over cleanly, and every window size tried
reaches full coverage.

Signed-off-by: Sébastien Taylor <[email protected]>
@sebastient sebastient added the ci:full Run the Full CI tier (host matrix, HAL extras, scancode, Sonar) label Sep 16, 2026
@sebastient
sebastient requested a balanced review from Copilot September 16, 2026 02:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Four unresolved moderate findings remain in the mutation workflow and ANGLE extraction script.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (3)

.github/workflows/mutants.yml:280

  • With set -o pipefail, this pipeline can fail the report step whenever a shard has more than 60 missed mutants: head exits after 60 lines, cat receives SIGPIPE (exit 141), and the enclosing grouped command makes the job fail even though the report was produced. Stream through a reader that consumes all input (for example sed -n '1,60p') or write the aggregate to a file before truncating it.
              cat shards/mutants-shard-*/mutants.out/missed.txt 2>/dev/null | head -60

.github/workflows/mutants.yml:280

  • With set -o pipefail, this aggregation fails when any shard has no surviving mutants: cargo-mutants omits empty result files, so the glob passes nonexistent missed.txt paths to cat. If another shard has a miss, this branch is entered and the report job fails instead of publishing the summary; filter the glob to existing files before aggregating.
              cat shards/mutants-shard-*/mutants.out/missed.txt 2>/dev/null | head -60

scripts/fetch-angle.sh:253

  • When a required framework directory is absent from the staged payload, continue leaves any old directory under ${DEST} in place. The post-extraction checks can then pass with stale EGL plus newly staged GLES, silently accepting a mixed/partial package instead of rejecting it. Require both framework entries before moving them (or remove the old entries before this check).
        [[ -e "${SRC}/${item}" ]] || continue
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Lite (auto)

Note

Copilot is running an experiment and ran this review at Lite.

Comment on lines +69 to +74
total="${TOTAL_IN:-64}"
window="${WINDOW_IN:-8}"
if (( window > total )); then
echo "::error::window ${window} exceeds ${total} shards"
exit 1
fi
@sonarqubecloud

Copy link
Copy Markdown

@sebastient
sebastient merged commit 61b5405 into main Sep 16, 2026
34 checks passed
@sebastient
sebastient deleted the chore/EDGEAI-1554-repin-advisories branch September 16, 2026 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:full Run the Full CI tier (host matrix, HAL extras, scancode, Sonar)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants