Skip to content

Commit d6d34a1

Browse files
committed
ci: make CI standalone (drop estate reusable workflows + third-party setup action)
The PR-gating build/lint/test were blocked by startup_failure on workflows with external dependencies. Make the gating CI self-contained: - ci.yml: self-host the OCaml toolchain via apt + opam (replacing third-party ocaml/setup-ocaml); use only first-party actions/* at real upstream major tags. The previous SHA pins carried fictional version comments (checkout "v6.0.3", upload-artifact "v7.0.1" — nonexistent upstream). dune-project needs OCaml >= 4.14, satisfied by the runner's apt OCaml (ocaml-system) with a base-compiler fallback. - governance.yml: replace the hyperpolymath/standards governance-reusable with a conservative, delta-aware local gate (tools/ci/governance-standalone.sh): Jekyll-artifact ban, MPL-1.0 SPDX-header ban, PR-delta DOC-FORMAT check. Verified to pass clean on the current tree. - secret-scanner.yml: replace the hyperpolymath/standards secret-scanner-reusable (which needed inherited secrets) with a pure-shell high-confidence scan (tools/ci/secret-scan-standalone.sh). No secrets required. - scorecard.yml: call ossf/scorecard-action directly (mirroring the already- direct scorecard-enforcer.yml), dropping the estate reusable. Root cause of the governance/secret-scanner startup_failure: a concurrency block in a reusable-workflow caller stacks on the reusable's own concurrency declaration and is rejected at run-creation (the BP008 class documented in spark-theatre-gate.yml). The standalone replacements are normal workflows, so their concurrency blocks are safe. Left intentionally (see PR): hypatia-scan + spark-theatre-gate (estate- proprietary scanners, currently passing — reproducing locally would lose coverage), mirror (cross-forge by nature), and release.yml's ocaml/setup-ocaml (cross-platform macOS matrix; a Linux-only inline setup would break it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lz7pRcec2Z3tVtaAhvB3M8
1 parent ac98c81 commit d6d34a1

6 files changed

Lines changed: 251 additions & 60 deletions

File tree

.github/workflows/ci.yml

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
# SPDX-License-Identifier: MPL-2.0
2+
#
3+
# Standalone CI: no dependency on third-party actions or external-repo
4+
# reusable workflows. The OCaml toolchain is self-hosted via apt + opam
5+
# (replacing ocaml/setup-ocaml), and only first-party `actions/*` are used
6+
# (checkout / setup-node / upload-artifact), referenced by upstream major
7+
# tag. dune-project requires OCaml >= 4.14, satisfied by the runner's apt
8+
# OCaml (ocaml-system), with a base-compiler fallback.
9+
#
10+
# NOTE on pins: the previous SHA pins carried fictional version comments
11+
# (`actions/checkout # v6.0.3`, `actions/upload-artifact # v7.0.1` — versions
12+
# that do not exist upstream), so they were re-pointed to real major tags.
13+
# Re-pin to verified upstream SHAs if the repo's SHA-pinning policy requires
14+
# it (these are GitHub-first-party actions, always permitted under any
15+
# "allowed actions" policy).
216
name: CI
317
on:
418
push:
@@ -9,27 +23,36 @@ permissions:
923
contents: read
1024
# Actions concurrency pool. Applied only to read-only check workflows
1125
# (no publish/mutation), so cancelling a superseded run is always safe.
26+
# Safe here: this is a normal workflow (not a reusable-workflow caller),
27+
# so there is no caller/reusable concurrency stacking (the BP008 startup
28+
# failure class).
1229
concurrency:
1330
group: ${{ github.workflow }}-${{ github.ref }}
1431
cancel-in-progress: true
1532
jobs:
1633
build:
1734
runs-on: ubuntu-latest
18-
timeout-minutes: 10
35+
timeout-minutes: 25
1936

2037
steps:
2138
- name: Checkout code
22-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
23-
- name: Set up OCaml
24-
uses: ocaml/setup-ocaml@e32b06a3e831ff2fbc6f08cf35be2085e3918014 # v3
25-
with:
26-
ocaml-compiler: "5.1"
39+
uses: actions/checkout@v4
40+
- name: Set up OCaml toolchain (self-hosted; replaces ocaml/setup-ocaml)
41+
run: |
42+
sudo apt-get update
43+
sudo apt-get install -y --no-install-recommends opam ocaml
44+
opam init --bare --disable-sandboxing --yes
45+
# Prefer the runner's system OCaml (>= 4.14 satisfies dune-project)
46+
# for an instant switch; fall back to a pinned base compiler.
47+
opam switch create . ocaml-system --no-install --yes \
48+
|| opam switch create . ocaml-base-compiler.4.14.2 --no-install --yes
49+
opam exec -- ocaml -version
2750
- name: Set up Node.js
28-
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
51+
uses: actions/setup-node@v4
2952
with:
3053
node-version: "20"
3154
- name: Install dependencies
32-
run: opam install . --deps-only --with-test --with-doc
55+
run: opam install . --deps-only --with-test --with-doc --yes
3356
- name: Install tree-sitter CLI (for res-to-affine walker tests)
3457
# Same rationale as the migration-assistant job (see below):
3558
# npm distribution is the fast CI install (~5 s). The walker
@@ -72,21 +95,25 @@ jobs:
7295
run: opam exec -- dune build @fmt
7396
lint:
7497
runs-on: ubuntu-latest
75-
timeout-minutes: 10
98+
timeout-minutes: 25
7699

77100
steps:
78101
- name: Checkout code
79-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
80-
- name: Set up OCaml
81-
uses: ocaml/setup-ocaml@e32b06a3e831ff2fbc6f08cf35be2085e3918014 # v3
82-
with:
83-
ocaml-compiler: "5.1"
102+
uses: actions/checkout@v4
103+
- name: Set up OCaml toolchain (self-hosted; replaces ocaml/setup-ocaml)
104+
run: |
105+
sudo apt-get update
106+
sudo apt-get install -y --no-install-recommends opam ocaml
107+
opam init --bare --disable-sandboxing --yes
108+
opam switch create . ocaml-system --no-install --yes \
109+
|| opam switch create . ocaml-base-compiler.4.14.2 --no-install --yes
110+
opam exec -- ocaml -version
84111
- name: Install dependencies
85112
# Match the build job: `dune build` compiles everything including
86113
# test/ (which depends on alcotest, with-test) and the @doc target
87114
# below (which depends on odoc, with-doc). Without these flags, lint
88115
# fails on missing alcotest before it ever reaches the doc step.
89-
run: opam install . --deps-only --with-test --with-doc
116+
run: opam install . --deps-only --with-test --with-doc --yes
90117
- name: Build
91118
run: opam exec -- dune build
92119
- name: Lint with odoc
@@ -97,17 +124,21 @@ jobs:
97124
# §"Bench standards". Does NOT block merge. Promotion to a
98125
# ratcheted gate requires a calibrated baseline first.
99126
runs-on: ubuntu-latest
100-
timeout-minutes: 10
127+
timeout-minutes: 25
101128

102129
steps:
103130
- name: Checkout code
104-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
105-
- name: Set up OCaml
106-
uses: ocaml/setup-ocaml@e32b06a3e831ff2fbc6f08cf35be2085e3918014 # v3
107-
with:
108-
ocaml-compiler: "5.1"
131+
uses: actions/checkout@v4
132+
- name: Set up OCaml toolchain (self-hosted; replaces ocaml/setup-ocaml)
133+
run: |
134+
sudo apt-get update
135+
sudo apt-get install -y --no-install-recommends opam ocaml
136+
opam init --bare --disable-sandboxing --yes
137+
opam switch create . ocaml-system --no-install --yes \
138+
|| opam switch create . ocaml-base-compiler.4.14.2 --no-install --yes
139+
opam exec -- ocaml -version
109140
- name: Install dependencies
110-
run: opam install . --deps-only --with-test --with-doc
141+
run: opam install . --deps-only --with-test --with-doc --yes
111142
- name: Build bench targets
112143
run: opam exec -- dune build @bench --force
113144
continue-on-error: true
@@ -133,7 +164,7 @@ jobs:
133164
} >> "$GITHUB_STEP_SUMMARY"
134165
- name: Upload bench log
135166
if: always()
136-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
167+
uses: actions/upload-artifact@v4
137168
with:
138169
name: bench-output
139170
path: bench-output.log
@@ -143,17 +174,21 @@ jobs:
143174
# docs/standards/TESTING.adoc §"Coverage (visibility-only)".
144175
# No merge-blocking floor today.
145176
runs-on: ubuntu-latest
146-
timeout-minutes: 10
177+
timeout-minutes: 25
147178

148179
steps:
149180
- name: Checkout code
150-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
151-
- name: Set up OCaml
152-
uses: ocaml/setup-ocaml@e32b06a3e831ff2fbc6f08cf35be2085e3918014 # v3
153-
with:
154-
ocaml-compiler: "5.1"
181+
uses: actions/checkout@v4
182+
- name: Set up OCaml toolchain (self-hosted; replaces ocaml/setup-ocaml)
183+
run: |
184+
sudo apt-get update
185+
sudo apt-get install -y --no-install-recommends opam ocaml
186+
opam init --bare --disable-sandboxing --yes
187+
opam switch create . ocaml-system --no-install --yes \
188+
|| opam switch create . ocaml-base-compiler.4.14.2 --no-install --yes
189+
opam exec -- ocaml -version
155190
- name: Install dependencies
156-
run: opam install . --deps-only --with-test --with-doc
191+
run: opam install . --deps-only --with-test --with-doc --yes
157192
- name: Run tests with bisect_ppx instrumentation
158193
run: |
159194
opam exec -- dune runtest --force --instrument-with bisect_ppx
@@ -178,7 +213,7 @@ jobs:
178213
} >> "$GITHUB_STEP_SUMMARY"
179214
- name: Upload coverage HTML
180215
if: always()
181-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
216+
uses: actions/upload-artifact@v4
182217
with:
183218
name: coverage-html
184219
path: _coverage
@@ -203,9 +238,9 @@ jobs:
203238

204239
steps:
205240
- name: Checkout code
206-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
241+
uses: actions/checkout@v4
207242
- name: Set up Node.js
208-
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
243+
uses: actions/setup-node@v4
209244
with:
210245
node-version: "20"
211246
- name: Install test runner dependencies
@@ -248,9 +283,9 @@ jobs:
248283

249284
steps:
250285
- name: Checkout code
251-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
286+
uses: actions/checkout@v4
252287
- name: Set up Node.js
253-
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
288+
uses: actions/setup-node@v4
254289
with:
255290
node-version: "20"
256291
- name: Install tree-sitter CLI

.github/workflows/governance.yml

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,38 @@
11
# SPDX-License-Identifier: MPL-2.0
2-
# governance.yml — single wrapper calling the shared estate governance bundle
3-
# in hyperpolymath/standards. Replaces ~8 duplicated per-repo governance
4-
# workflows (verisimiser#59 estate rollout). Load-bearing build/security
5-
# workflows (rust-ci, codeql, dependabot, release, secret-scanner, scorecard)
6-
# stay standalone in this repo.
2+
#
3+
# Standalone governance gate. Previously a thin caller of
4+
# `hyperpolymath/standards/.github/workflows/governance-reusable.yml@main`;
5+
# that cross-repo dependency (a) coupled this repo's CI to another repo's
6+
# moving `@main` and (b) startup-failed because a `concurrency:` block in a
7+
# reusable-workflow caller, when the reusable also declares concurrency on the
8+
# same key, is rejected at run-creation (the BP008 class — see
9+
# spark-theatre-gate.yml's note). This self-contained version runs the repo's
10+
# own conservative, delta-aware checks (tools/ci/governance-standalone.sh) and
11+
# is a normal workflow, so the concurrency block is safe to keep.
712
name: Governance
813
on:
914
push:
1015
branches: [main, master]
1116
pull_request:
1217
workflow_dispatch:
13-
# Estate guardrail: cancel superseded runs so re-pushes / rebased PR
14-
# updates do not pile up queued runs against the shared account-wide
15-
# Actions concurrency pool. Applied only to read-only check workflows
16-
# (no publish/mutation), so cancelling a superseded run is always safe.
1718
concurrency:
1819
group: ${{ github.workflow }}-${{ github.ref }}
1920
cancel-in-progress: true
2021
permissions:
2122
contents: read
2223
jobs:
2324
governance:
24-
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@main
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 5
27+
steps:
28+
- name: Checkout code
29+
uses: actions/checkout@v4
30+
with:
31+
fetch-depth: 0
32+
- name: Fetch base ref (DOC-FORMAT delta)
33+
if: github.event_name == 'pull_request'
34+
run: git fetch --no-tags origin "+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/${GITHUB_BASE_REF}"
35+
- name: Run governance gate
36+
env:
37+
GITHUB_BASE_REF: ${{ github.base_ref }}
38+
run: ./tools/ci/governance-standalone.sh

.github/workflows/scorecard.yml

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,42 @@
11
# SPDX-License-Identifier: MPL-2.0
2+
#
3+
# Standalone OpenSSF Scorecard. Previously a thin caller of
4+
# `hyperpolymath/standards/.github/workflows/scorecard-reusable.yml`; that
5+
# cross-repo dependency had a persistent startup_failure history (see the
6+
# prior note in this file's git history). This self-contained version calls
7+
# `ossf/scorecard-action` directly — mirroring the already-direct sibling
8+
# `scorecard-enforcer.yml` — so there is no external-repo workflow dependency.
9+
# `ossf/scorecard-action` / `github/codeql-action` stay SHA-pinned (third-party
10+
# actions); they match the pins used in scorecard-enforcer.yml.
211
name: Scorecards supply-chain security
312
on:
413
branch_protection_rule:
514
schedule:
615
- cron: '23 4 * * 1'
716
push:
817
branches: [main]
9-
# Workflow-level permissions are read-only. The job that calls the
10-
# reusable upgrades to the writes required by `ossf/scorecard-action`.
11-
# Job-level permissions REPLACE workflow-level for the job, so a bare
12-
# `permissions: read-all` at workflow level + writes at job level is
13-
# equivalent to `contents: read` at workflow level — both yield the
14-
# same effective set at the job. We match the canonical caller in
15-
# `hyperpolymath/standards/.github/workflows/scorecard-reusable.yml`
16-
# (and the known-good sibling julia-professional-registry/scorecard.yml)
17-
# to eliminate the prior `read-all` / `contents: read` divergence as a
18-
# possible cause of the persistent `startup_failure` (every run since
19-
# adoption — see PR #457's test-plan delta).
2018
permissions:
2119
contents: read
2220
jobs:
2321
analysis:
22+
name: Scorecard analysis
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 10
2425
permissions:
2526
security-events: write
2627
id-token: write
27-
uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@e03686486e11b662834d7090dffae54c3e96fd59
28-
secrets: inherit
28+
steps:
29+
- name: Checkout code
30+
uses: actions/checkout@v4
31+
with:
32+
persist-credentials: false
33+
- name: Run analysis
34+
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
35+
with:
36+
results_file: results.sarif
37+
results_format: sarif
38+
publish_results: true
39+
- name: Upload SARIF to code-scanning
40+
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3
41+
with:
42+
sarif_file: results.sarif

.github/workflows/secret-scanner.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
# SPDX-License-Identifier: MPL-2.0
2+
#
3+
# Standalone secret scan. Previously a thin caller of
4+
# `hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml`
5+
# with `secrets: inherit`; that cross-repo dependency startup-failed (the
6+
# caller's `concurrency:` block stacked on the reusable's — the BP008 class,
7+
# see spark-theatre-gate.yml) and required inheriting org secrets. This
8+
# self-contained version runs a pure-shell high-confidence scan
9+
# (tools/ci/secret-scan-standalone.sh), needs no secrets, and as a normal
10+
# workflow can keep its concurrency block.
211
name: Secret Scanner
312
on:
413
pull_request:
@@ -11,5 +20,10 @@ permissions:
1120
contents: read
1221
jobs:
1322
scan:
14-
uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@3e4bd4c93911750727e2e4c66dff859e00079da0
15-
secrets: inherit
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 5
25+
steps:
26+
- name: Checkout code
27+
uses: actions/checkout@v4
28+
- name: Run standalone secret scan
29+
run: ./tools/ci/secret-scan-standalone.sh

tools/ci/governance-standalone.sh

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
#
4+
# Standalone governance gate.
5+
#
6+
# Replaces the external `hyperpolymath/standards` governance-reusable.yml so
7+
# this repo's CI carries no cross-repo workflow dependency. It is deliberately
8+
# conservative and delta-aware: it enforces the unambiguous estate rules that
9+
# the current tree already satisfies, and treats DOC-FORMAT as a PR-delta
10+
# check (matching the canonical "on any PR that adds a docs/ .md" semantics)
11+
# so the 59 pre-existing docs/*.md files are never retro-flagged.
12+
#
13+
# This is a best-effort local reimplementation; it is NOT byte-for-byte the
14+
# canonical estate bundle (which is not visible from this repo). See the
15+
# `standalone-ci` PR for the trade-off discussion.
16+
#
17+
# Usage: tools/ci/governance-standalone.sh [BASE_REF]
18+
# BASE_REF (or $GOVERNANCE_BASE_REF / $GITHUB_BASE_REF) enables the
19+
# DOC-FORMAT delta check; if absent the delta check is skipped.
20+
set -uo pipefail
21+
22+
fail=0
23+
note() { printf ' %s\n' "$*"; }
24+
err() { printf '::error::%s\n' "$*"; fail=1; }
25+
26+
echo "== Governance gate (standalone) =="
27+
28+
# --- [1] Jekyll artifacts are banned (estate SSG is hyperpolymath/casket-ssg)
29+
echo "[1/3] Jekyll artifacts"
30+
jekyll=$(find . \( -path ./.git -o -path ./_build -o -path ./node_modules \) -prune -o \
31+
\( -name '_config.yml' -o -name 'Gemfile' -o -iname 'jekyll*.yml' \
32+
-o -iname 'jekyll-gh-pages*.yml' \) -print 2>/dev/null || true)
33+
if [ -n "$jekyll" ]; then
34+
err "Jekyll artifacts present (migrate to hyperpolymath/casket-ssg):"
35+
printf '%s\n' "$jekyll" | sed 's/^/ /'
36+
else note "none"; fi
37+
38+
# --- [2] MPL-1.0 license headers are banned (must be MPL-2.0) ---------------
39+
# Match the actual SPDX identifier, not mere prose mentions of the ban (the
40+
# policy docs legitimately discuss MPL-1.0).
41+
echo "[2/3] MPL-1.0 SPDX headers"
42+
mpl1=$(grep -rIl --exclude-dir=.git --exclude-dir=_build --exclude-dir=node_modules \
43+
-E 'SPDX-License-Identifier:[[:space:]]*MPL-1\.0' . 2>/dev/null || true)
44+
if [ -n "$mpl1" ]; then
45+
err "MPL-1.0 SPDX headers found (rewrite to MPL-2.0):"
46+
printf '%s\n' "$mpl1" | sed 's/^/ /'
47+
else note "none"; fi
48+
49+
# --- [3] DOC-FORMAT (delta): newly-added docs/ files must be .adoc ----------
50+
# Community-health files keep their canonical .md names.
51+
echo "[3/3] DOC-FORMAT (newly-added docs/ files must be .adoc)"
52+
base="${1:-${GOVERNANCE_BASE_REF:-${GITHUB_BASE_REF:-}}}"
53+
if [ -n "$base" ] && git rev-parse --verify --quiet "origin/$base" >/dev/null 2>&1; then
54+
added=$(git diff --name-only --diff-filter=A "origin/$base...HEAD" -- 'docs/' 2>/dev/null \
55+
| grep -E '\.md$' \
56+
| grep -viE '/(CONTRIBUTING|CODE_OF_CONDUCT|SECURITY|CHANGELOG|README)\.md$' || true)
57+
if [ -n "$added" ]; then
58+
err "new docs/ .md files added (rename to .adoc per DOC-FORMAT):"
59+
printf '%s\n' "$added" | sed 's/^/ /'
60+
else note "no newly-added docs/ .md files"; fi
61+
else
62+
note "no base ref available — delta check skipped"
63+
fi
64+
65+
echo
66+
if [ "$fail" -ne 0 ]; then echo "Governance gate: FAIL"; exit 1; fi
67+
echo "Governance gate: PASS"

0 commit comments

Comments
 (0)