Skip to content

Commit 76825cc

Browse files
committed
fix(ci): remove invalid timeout-minutes on reusable workflow calls, sync workflows
1 parent df32340 commit 76825cc

11 files changed

Lines changed: 386 additions & 2 deletions
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
name: container build
3+
on:
4+
pull_request:
5+
paths:
6+
- 'container/**'
7+
- 'build/just/container.just'
8+
- '.github/workflows/container-build.yml'
9+
push:
10+
tags: ['v*']
11+
workflow_dispatch:
12+
13+
# Scope + cancel superseded runs (estate guardrail).
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
container:
23+
# OFF by default — costs nothing in derived repos. A repo opts in by setting
24+
# the repository/organisation variable CONTAINER_CI=true. When enabled it
25+
# runs on OWNED self-hosted runners (no metered GitHub Actions minutes);
26+
# override the labels with the CONTAINER_RUNNER variable (a JSON array).
27+
if: vars.CONTAINER_CI == 'true'
28+
runs-on: ${{ fromJSON(vars.CONTAINER_RUNNER || '["self-hosted","owned","container"]') }}
29+
timeout-minutes: 30
30+
permissions:
31+
contents: read
32+
steps:
33+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
34+
35+
- name: Tooling check
36+
run: |
37+
command -v just >/dev/null 2>&1 || { echo "::error::just not found on this runner"; exit 1; }
38+
# The container recipes auto-detect the engine (podman | nerdctl | docker).
39+
for e in podman nerdctl docker; do command -v "$e" >/dev/null 2>&1 && { echo "engine: $e"; break; }; done
40+
41+
- name: Build image
42+
run: just container-build
43+
44+
- name: Verify compose configuration
45+
run: just container-verify
46+
47+
- name: Scan image (trivy, best-effort)
48+
run: |
49+
if command -v trivy >/dev/null 2>&1; then
50+
trivy image --severity HIGH,CRITICAL --exit-code 0 "${{ github.event.repository.name }}:latest" || true
51+
else
52+
echo "trivy not installed on this runner — skipping image scan"
53+
fi
54+
55+
- name: Sign & verify .ctp bundle (tags only)
56+
if: startsWith(github.ref, 'refs/tags/v')
57+
run: just container-sign # cerro-torre: build + pack + Ed25519 sign + verify

.github/workflows/estate-rules.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# Estate Rules — enforces hyperpolymath estate-wide conventions:
5+
# * root shape (allowlist of permitted root entries)
6+
# * AsciiDoc-by-default (no .md files under docs/)
7+
# * V-lang is banned (no V-lang scaffolding or references; Zig is the FFI default)
8+
#
9+
# Each rule is enforced by an executable check script under scripts/. Failures
10+
# are surfaced as workflow errors so the rule can't silently regress.
11+
12+
name: Estate Rules
13+
on:
14+
push:
15+
branches: [main]
16+
pull_request:
17+
concurrency:
18+
group: ${{ github.workflow }}-${{ github.ref }}
19+
cancel-in-progress: true
20+
permissions:
21+
contents: read
22+
jobs:
23+
estate-rules:
24+
runs-on: ubuntu-latest
25+
timeout-minutes: 15
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
29+
- name: Root shape allowlist
30+
run: bash scripts/check-root-shape.sh .
31+
- name: AsciiDoc by default (no .md under docs/)
32+
run: bash scripts/check-no-md-in-docs.sh .
33+
- name: No V-lang references
34+
run: bash scripts/check-no-vlang.sh .

.github/workflows/guix-policy.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
name: Guix Package Policy
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
8+
# Estate guardrail: scope push to default branches so a PR fires once (not
9+
# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check.
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
14+
permissions:
15+
contents: read
16+
jobs:
17+
check:
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 15
20+
permissions:
21+
contents: read
22+
steps:
23+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
24+
- name: Enforce Guix-only package policy
25+
run: |
26+
# Guix is the sole package manager estate-wide. Nix is BANNED.
27+
HAS_GUIX=$(find . -path ./.git -prune -o \( -name "*.scm" -o -name ".guix-channel" -o -name "guix.scm" \) -print 2>/dev/null | head -1)
28+
HAS_NIX=$(find . -path ./.git -prune -o -name "*.nix" -print 2>/dev/null | head -1)
29+
30+
# Hard-fail on any Nix file — Nix is banned, migrate to Guix.
31+
if [ -n "$HAS_NIX" ]; then
32+
echo "::error::Nix is banned estate-wide (Guix only). Remove flake.nix/*.nix and use guix.scm: $HAS_NIX"
33+
exit 1
34+
fi
35+
36+
# Warn on non-reproducible lock files; prefer Guix manifests.
37+
NEW_LOCKS=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E 'package-lock\.json|yarn\.lock|Gemfile\.lock|Pipfile\.lock|poetry\.lock|cargo\.lock' || true)
38+
if [ -n "$NEW_LOCKS" ]; then
39+
echo "⚠️ Lock files detected. Prefer Guix manifests for reproducibility."
40+
fi
41+
42+
if [ -n "$HAS_GUIX" ]; then
43+
echo "✅ Guix package management detected"
44+
else
45+
echo "ℹ️ Consider adding guix.scm / .guix-channel for reproducible builds"
46+
fi
47+
48+
echo "✅ Guix package policy check passed"

.github/workflows/hypatia-scan.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
#
3+
# Standalone Hypatia security scan (push / PR / weekly). This is NOT a duplicate
4+
# of the `hypatia-scan` job in `static-analysis-gate.yml`: that job exists to
5+
# feed the gitbot-fleet LEARNING pipeline (via `deposit-findings`), whereas this
6+
# workflow is the repo's independent security scan. Same tool, two consumers.
7+
# See .github/workflows/README.adoc.
8+
name: Hypatia Security Scan
9+
10+
on:
11+
push:
12+
branches: [main, master, develop]
13+
pull_request:
14+
branches: [main, master]
15+
schedule:
16+
- cron: '0 0 * * 0'
17+
workflow_dispatch:
18+
19+
concurrency:
20+
group: ${{ github.workflow }}-${{ github.ref }}
21+
cancel-in-progress: true
22+
permissions:
23+
contents: read
24+
security-events: read
25+
26+
jobs:
27+
scan:
28+
uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@09e7023d24682621bea4e11965a1ef5e87d86c3b
29+
timeout-minutes: 20

.github/workflows/instant-sync.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Instant Forge Sync - Triggers propagation to all forges on push/release
3+
name: Instant Sync
4+
on:
5+
push:
6+
branches: [main, master]
7+
release:
8+
types: [published]
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: false
12+
permissions:
13+
contents: read
14+
jobs:
15+
dispatch:
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 15
18+
steps:
19+
- name: Gate on secret presence
20+
id: gate
21+
env:
22+
FARM_DISPATCH_TOKEN: ${{ secrets.FARM_DISPATCH_TOKEN }}
23+
run: |
24+
if [ -n "${FARM_DISPATCH_TOKEN}" ]; then
25+
echo "present=true" >> "$GITHUB_OUTPUT"
26+
else
27+
echo "present=false" >> "$GITHUB_OUTPUT"
28+
echo "::notice::FARM_DISPATCH_TOKEN not set — skipping forge propagation (expected on forks)."
29+
fi
30+
- name: Trigger Propagation
31+
if: steps.gate.outputs.present == 'true'
32+
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v3
33+
with:
34+
token: ${{ secrets.FARM_DISPATCH_TOKEN }}
35+
repository: hyperpolymath/.git-private-farm
36+
event-type: propagate
37+
client-payload: |-
38+
{
39+
"repo": "${{ github.event.repository.name }}",
40+
"ref": "${{ github.ref }}",
41+
"sha": "${{ github.sha }}",
42+
"forges": ""
43+
}
44+
- name: Confirm
45+
env:
46+
REPO_NAME: ${{ github.event.repository.name }}
47+
run: echo "::notice::Propagation triggered for ${REPO_NAME}"

.github/workflows/mirror.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
name: Mirror to Git Forges
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
concurrency:
8+
group: ${{ github.workflow }}-${{ github.ref }}
9+
cancel-in-progress: false
10+
permissions:
11+
contents: read
12+
jobs:
13+
mirror:
14+
uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@09e7023d24682621bea4e11965a1ef5e87d86c3b
15+
timeout-minutes: 10
16+
secrets: inherit

.github/workflows/pages.yml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# GitHub Pages via casket-ssg (hyperpolymath's pure-Haskell static site generator).
3+
# Replaces the orphan one-off Pages deployment with a reproducible build.
4+
name: GitHub Pages
5+
6+
on:
7+
push:
8+
branches: [main]
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
pages: write
14+
id-token: write
15+
16+
# Serialise Pages deploys; never cancel an in-flight deploy.
17+
concurrency:
18+
group: "pages"
19+
cancel-in-progress: false
20+
21+
jobs:
22+
build:
23+
timeout-minutes: 20
24+
runs-on: ubuntu-latest
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
28+
29+
- name: Checkout casket-ssg
30+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
31+
with:
32+
repository: hyperpolymath/casket-ssg
33+
path: .casket-ssg
34+
35+
- name: Setup GHCup
36+
uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0
37+
with:
38+
ghc-version: '9.8.2'
39+
cabal-version: '3.10'
40+
41+
- name: Cache Cabal
42+
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v4
43+
with:
44+
path: |
45+
~/.cabal/packages
46+
~/.cabal/store
47+
.casket-ssg/dist-newstyle
48+
key: ${{ runner.os }}-casket-${{ hashFiles('.casket-ssg/casket-ssg.cabal') }}
49+
50+
- name: Build casket-ssg
51+
working-directory: .casket-ssg
52+
run: cabal build
53+
54+
- name: Build site
55+
run: |
56+
mkdir -p site _site
57+
# Seed site/index.md from README if the author hasn't provided one.
58+
if [ ! -f site/index.md ]; then
59+
{
60+
echo "---"
61+
echo "title: $(basename "$PWD")"
62+
echo "date: $(date +%Y-%m-%d)"
63+
echo "---"
64+
if [ -f README.adoc ]; then cat README.adoc
65+
elif [ -f README.md ]; then cat README.md
66+
else printf '\n# %s\n\nDocumentation coming soon.\n' "$(basename "$PWD")"
67+
fi
68+
} > site/index.md
69+
fi
70+
cd .casket-ssg && cabal run casket-ssg -- build ../site ../_site
71+
72+
- name: Setup Pages
73+
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
74+
75+
- name: Upload artifact
76+
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v3
77+
with:
78+
path: '_site'
79+
80+
deploy:
81+
timeout-minutes: 20
82+
environment:
83+
name: github-pages
84+
url: ${{ steps.deployment.outputs.page_url }}
85+
runs-on: ubuntu-latest
86+
needs: build
87+
steps:
88+
- name: Deploy to GitHub Pages
89+
id: deployment
90+
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Dormant push-email notification. ARMED by setting the repo variable
3+
# PUSH_EMAIL_ENABLED=true (the single on/off switch). Addresses are pre-filled;
4+
# sending needs the org SMTP secrets (SMTP_HOST/PORT/USER/PASS). Inherited by
5+
# new repos from the template; placed on existing repos by the farm sweep.
6+
name: Push email notification
7+
on:
8+
push: {}
9+
permissions:
10+
contents: read
11+
jobs:
12+
notify:
13+
name: Email on push
14+
if: ${{ vars.PUSH_EMAIL_ENABLED == 'true' }}
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Send push notification email
18+
uses: dawidd6/action-send-mail@6e502825a508b867ab2954ad6343b68787624c01 # pinned
19+
with:
20+
server_address: ${{ secrets.SMTP_HOST }}
21+
server_port: ${{ secrets.SMTP_PORT }}
22+
secure: true
23+
username: ${{ secrets.SMTP_USER }}
24+
password: ${{ secrets.SMTP_PASS }}
25+
from: "GitHub Push <${{ secrets.SMTP_USER }}>"
26+
to: "jonathan.jewell@gmail.com j.d.a.jewell@open.ac.uk"
27+
subject: "[${{ github.repository }}] push to ${{ github.ref_name }} by ${{ github.actor }}"
28+
body: |
29+
Repository: ${{ github.repository }}
30+
Branch: ${{ github.ref_name }}
31+
Pusher: ${{ github.actor }}
32+
Compare: ${{ github.event.compare }}
33+
Head msg: ${{ github.event.head_commit.message }}

.github/workflows/rust-ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,3 @@ permissions:
1212
jobs:
1313
rust-ci:
1414
uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@412a7031577112b31ee287cc6060179d638d6500
15-
timeout-minutes: 10

.github/workflows/secret-scanner.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ permissions:
1212
jobs:
1313
scan:
1414
uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@3e4bd4c93911750727e2e4c66dff859e00079da0
15-
timeout-minutes: 10
1615
secrets: inherit
1716
trufflehog:
1817
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)