EDGEAI-1554: Re-pin shared CI, and run advisories ungated - #194
Conversation
There was a problem hiding this comment.
🔵 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-gateonly 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, leavinglastempty and causing an unchangedmainto 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:15andCONTRIBUTING.md:368still say Nightly runs only whenmainmoves and includes cargo hack. Withadvisoriesintentionally ungated andhack-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]>
eaa14de to
5c3beb1
Compare
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]>
There was a problem hiding this comment.
🟡 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:headexits after 60 lines,catreceives 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 examplesed -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 nonexistentmissed.txtpaths tocat. 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,
continueleaves 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.
| total="${TOTAL_IN:-64}" | ||
| window="${WINDOW_IN:-8}" | ||
| if (( window > total )); then | ||
| echo "::error::window ${window} exceeds ${total} shards" | ||
| exit 1 | ||
| fi |
|



Picks up EdgeFirstAI/.github#26 —
a9b62d89→797eea74— plus two fixes and a restructure.1. The nightly gate now actually gates
It applied its "has
mainmoved" check only onschedule, so aworkflow_dispatchbypassed it silently. It also compared against the last successful run, and hal's last green nightly onmainwas 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 (
successorfailure;cancelledand friends are discarded, since a run killed before its lanes start proves nothing). Aforceinput is forwarded for deliberately re-running an unchanged commit. The|| falsefallback is load-bearing —inputsis null on a schedule trigger.2.
cargo auditruns ungatedIt moved out of
rust-fullintoadvisories.ymland is called withoutneeds: 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 aCargo.locknobody had touched — a gated lane would have gone quiet exactly then.Free hard-coded runner, parses
Cargo.lockwithout building. With #193 merged this should now be green.nightly-extrais also gone for hal: with audit removed it is only the hack lane, which skips whenhack-argsis empty. It used to boot a runner and install two tools to run the audit alone.3.
fetch-angle.sh— the macOS failureRoot cause, and it was never about the release asset:
When
${DEST}/EGL.xcframeworkalready exists — which a partially restoredtarget/cache leaves behind —mvrefuses to merge one directory into another and fails. The redirect hid the message,|| truehid the status, so the payload stayed in${DEST}/distand the script ran on to fail in thecpwith "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:
crates/decodercrates/tensorcrates/trackerAs 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.ymlnow 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-placestays 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:
Not yet verified
workflow_dispatchis unavailable for a workflow that is not on the default branch, somutants.ymlcannot be test-run until this merges — its first real execution will be the first scheduled one. The shard arithmetic is verified; thecargo mutants --shardinvocation and per-shard timing on a free runner are not.ci:fullis on this PR so the macOS lanes actually exercise thefetch-angle.shfix; Quick does not run macOS.