|
| 1 | += Sweep classifiers |
| 2 | +:SPDX-License-Identifier: PMPL-1.0-or-later |
| 3 | + |
| 4 | +Per-template classifiers used to triage the wrapper-sweep work that |
| 5 | +follows each of the foundational reusable PRs filed against |
| 6 | +`standards`: |
| 7 | + |
| 8 | +[cols="2,1,3", options="header"] |
| 9 | +|=== |
| 10 | +| Classifier | Reusable PR | Template |
| 11 | +| `classify-mirror.sh` | #187 | `mirror.yml` (7-forge mirror bundle) |
| 12 | +| `classify-secret-scanner.sh` | #190 | `secret-scanner.yml` (trufflehog/gitleaks/rust/shell) |
| 13 | +| `classify-codeql.sh` | #192 | `codeql.yml` (CodeQL security analysis) |
| 14 | +| `classify-hypatia-scan.sh` | #193 | `hypatia-scan.yml` (Hypatia neurosymbolic scan) |
| 15 | +|=== |
| 16 | + |
| 17 | +== Pipeline |
| 18 | + |
| 19 | +. Paginate `gh api /search/code` for the template across the estate. |
| 20 | +. Group results by blob SHA; fetch each unique blob exactly once. |
| 21 | +. Classify each blob (job-set match / line-count band / language matrix). |
| 22 | +. Emit per-repo TSV: `<repo>\t<sha>\t<class>\t<reason>\t<lines>\t<details>`. |
| 23 | + |
| 24 | +The expensive step (blob fetch) is cached in `$BLOBS_DIR`, so reruns |
| 25 | +are fast. |
| 26 | + |
| 27 | +== Usage |
| 28 | + |
| 29 | +[source,bash] |
| 30 | +---- |
| 31 | +# 1. Paginate the search (one-time per template): |
| 32 | +gh api --paginate -X GET '/search/code' \ |
| 33 | + -f q='filename:mirror.yml path:.github/workflows org:hyperpolymath' \ |
| 34 | + --jq '.items[] | {repo: .repository.name, sha: .sha}' \ |
| 35 | + > /tmp/mirror-full.json |
| 36 | +
|
| 37 | +# 2. Run the classifier: |
| 38 | +BLOBS_DIR=/tmp/mirror-blobs \ |
| 39 | + ./classify-mirror.sh /tmp/mirror-full.json > /tmp/mirror-classification.tsv |
| 40 | +
|
| 41 | +# 3. Summarise: |
| 42 | +awk -F'\t' '{print $3}' /tmp/mirror-classification.tsv | sort | uniq -c | sort -rn |
| 43 | +---- |
| 44 | + |
| 45 | +== Nested-path caveat — 3 layers of undercount |
| 46 | + |
| 47 | +`gh api /search/code` undercounts monorepo-nested workflow files in |
| 48 | +three compounding ways: |
| 49 | + |
| 50 | +. *Layer 1 — `path:` filter is a prefix match.* `path:.github/workflows` |
| 51 | + excludes paths like `a2ml/bindings/deno/.github/workflows/mirror.yml` |
| 52 | + outright. |
| 53 | +. *Layer 2 — even broad queries are org-scope-truncated.* Removing the |
| 54 | + `path:` filter and running `filename:codeql.yml org:hyperpolymath` |
| 55 | + returns ~190 paths, but per-repo enumeration of just |
| 56 | + `developer-ecosystem` finds 170 codeql.yml files. The endpoint |
| 57 | + silently caps results once it hits internal limits. Empirically |
| 58 | + validated against `scorecard.yml`: broad query saw 152 paths, all |
| 59 | + top-level; per-repo enumeration revealed 626 nested copies the |
| 60 | + broad query missed entirely. |
| 61 | +. *Layer 3 — nested workflows do not execute.* GitHub Actions only runs |
| 62 | + workflows from the repo-root `.github/workflows/` directory. Nested |
| 63 | + copies are inert vendored templates or stale leftover. Security-driven |
| 64 | + campaigns (e.g. propagating a missing guardrail) do NOT close |
| 65 | + additional attack surface via nested wrappers. Single-source-of-truth |
| 66 | + campaigns still benefit. |
| 67 | + |
| 68 | +For accurate counts, use `list-workflow-paths.sh` (this directory), |
| 69 | +which walks `gh repo list` and queries each repo's Git Tree API |
| 70 | +directly — bypassing Layers 1 and 2. |
| 71 | + |
| 72 | +[source,bash] |
| 73 | +---- |
| 74 | +./list-workflow-paths.sh codeql.yml \ |
| 75 | + > /tmp/drift-survey/codeql-all-tuples.tsv |
| 76 | +
|
| 77 | +# Top-level vs nested split: |
| 78 | +awk -F'\t' '{print $4}' /tmp/drift-survey/codeql-all-tuples.tsv \ |
| 79 | + | sort | uniq -c |
| 80 | +---- |
| 81 | + |
| 82 | +Output format: `<repo>\t<path>\t<blob-sha>\t<top-level|nested>`. |
| 83 | + |
| 84 | +The legacy `gh api /search/code` query is still useful as a quick first |
| 85 | +look, but `list-workflow-paths.sh` is the source of truth for any |
| 86 | +sweep-planning decision. |
| 87 | + |
| 88 | +== Output classes (varies by classifier) |
| 89 | + |
| 90 | +* `TRIVIAL` / `TRIVIAL_CURRENT` / `TRIVIAL_DEFAULT` — canonical match; |
| 91 | + pure mechanical wrapper conversion. |
| 92 | +* `MISSING_*` / `SLIM_*` / `OLDER_*` — propagation lag; same wrapper as |
| 93 | + TRIVIAL upgrades the workflow body on first run after merge. |
| 94 | +* `SINGLE_NON_DEFAULT` / `MULTI_LANGUAGE` — requires one or more input |
| 95 | + overrides at the call site. |
| 96 | +* `NEEDS_REVIEW` — extra jobs, line count out of any known band, or |
| 97 | + custom workflow body. Per-repo diff required before wrapper. |
| 98 | + |
| 99 | +== Related |
| 100 | + |
| 101 | +* `scripts/apply-baseline.sh` — Hypatia-finding baseline filter (paired |
| 102 | + with `scripts/tests/apply-baseline-test.sh`). |
| 103 | +* PRs #187, #190, #192, #193 — the reusables these classifiers triage. |
0 commit comments