Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,40 @@
Org-wide CI building blocks. Callable from any `resq-software/*` repo via
`uses: resq-software/.github/.github/workflows/<name>.yml@main`.

## `repo-standards.yml`

Template/standards conformance check. Validates that a repo has a detectable
`LICENSE`, a non-stub `README.md` with a title, and no leftover
`{{PLACEHOLDER}}` tokens or `ResQ README Template` scaffold markers.

It is already wired into `required.yml` (so every consumer repo inherits it
through the `required` status check) and into `required-gate.yml` (so this
repo dogfoods it). You don't call it directly unless you want a standalone
conformance job.

**Warn-by-default**, matching the org's audit → enforce pattern
(harden-runner audit, rulesets evaluate). Violations surface as
`::warning` annotations without failing the build. Once a repo is clean,
flip enforcement on by passing `repo-standards-strict: true` to
`required.yml`:

```yaml
jobs:
ci:
uses: resq-software/.github/.github/workflows/required.yml@main
with:
lang: rust
repo-standards-strict: true # turn conformance violations into a hard failure
```

### Inputs (when called directly)

| Input | Type | Default | Notes |
| :-- | :-- | :-- | :-- |
| `strict` | bool | `false` | Fail the job on any violation. Default warns only. |
| `require-license` | bool | `true` | Require a `LICENSE`/`COPYING` file at the repo root. |
| `readme-min-bytes` | string | `"500"` | README smaller than this is treated as a stub. |

## `security-scan.yml`

Defense-in-depth security scan. All third-party `uses:` refs are SHA-pinned
Expand Down
124 changes: 124 additions & 0 deletions .github/workflows/repo-standards.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright 2026 ResQ Software
# SPDX-License-Identifier: Apache-2.0
#
# Reusable repo-conformance check. Validates that a consumer repo meets
# the org template/standards baseline:
# * a detectable LICENSE file (Apache-2.0 across the org)
# * a non-stub README.md with a title and no leftover template tokens
# * no unrendered {{PLACEHOLDER}} tokens or template scaffold markers
#
# Wired into required.yml (consumer repos) and required-gate.yml (this
# repo), so the single `required` status check (gated by org ruleset
# `default-branch-baseline`, id 15191038) also covers template
# conformance. Warn-by-default to match the org's audit -> enforce
# rollout pattern (harden-runner audit, rulesets evaluate). Flip
# `strict: true` once a repo is clean to turn violations into a hard
# CI failure.
#
# Security: inputs that reach `run:` are forwarded through `env:` and
# referenced as "$VAR" to prevent template-time expansion of
# caller-controlled strings into the shell.

name: repo-standards

on:
workflow_call:
inputs:
strict:
description: Fail the job on any violation. Default warns (annotations only).
type: boolean
required: false
default: false
require-license:
description: Require a detectable LICENSE/COPYING file at the repo root.
type: boolean
required: false
default: true
readme-min-bytes:
description: Minimum README.md size in bytes; smaller is treated as a stub.
type: string
required: false
default: "500"

permissions:
contents: read

jobs:
repo-standards:
name: repo-standards
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2
with:
egress-policy: audit

- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Check repo conformance
env:
STRICT: ${{ inputs.strict }}
REQUIRE_LICENSE: ${{ inputs.require-license }}
README_MIN_BYTES: ${{ inputs.readme-min-bytes }}
run: |
set -eu
violations=0
note() {
printf '::warning title=repo-standards::%s\n' "$1"
violations=$((violations + 1))
}

# ── LICENSE ──────────────────────────────────────────────────────
# POSIX glob loop (not ls/find) so shellcheck stays happy and odd
# filenames are handled safely. Unmatched globs stay literal, so the
# `[ -e ]` test is false for them.
if [ "$REQUIRE_LICENSE" = "true" ]; then
license_found=no
for f in LICENSE LICENSE.* COPYING COPYING.*; do
if [ -e "$f" ]; then license_found=yes; break; fi
done
if [ "$license_found" = yes ]; then
echo "ok: LICENSE present"
else
note "No LICENSE/COPYING file at repo root (org standard: Apache-2.0)."
fi
fi

# ── README ───────────────────────────────────────────────────────
if [ ! -f README.md ]; then
note "No README.md at repo root."
else
bytes=$(wc -c < README.md | tr -d ' ')
if [ "$bytes" -lt "$README_MIN_BYTES" ]; then
note "README.md is a stub ($bytes bytes < $README_MIN_BYTES required)."
fi
if ! grep -qE '^[[:space:]]*(#[[:space:]]|<h1)' README.md; then
note "README.md has no top-level title (a '# Heading' or '<h1>')."
fi
# Count DISTINCT {{TOKEN}} occurrences, excluding the literal
# meta-example {{PLACEHOLDER}} (which docs legitimately mention).
# An unrendered template carries many distinct tokens
# ({{PROJECT_NAME}}, {{REPO}}, ...); a doc mentioning the syntax
# once should not trip. Threshold: 3 distinct tokens.
ph_count=$(grep -oE '\{\{[A-Z0-9_]+\}\}' README.md \
| grep -vxF '{{PLACEHOLDER}}' | sort -u | wc -l | tr -d ' ')
if [ "$ph_count" -ge 3 ]; then
note "README.md still has $ph_count distinct {{PLACEHOLDER}} tokens (unrendered template?)."
fi
if grep -q 'ResQ README Template' README.md; then
note "README.md still contains the template scaffold marker comment."
fi
fi

# ── Summary ──────────────────────────────────────────────────────
if [ "$violations" -eq 0 ]; then
echo "repo-standards: all checks passed."
exit 0
fi
echo "repo-standards: $violations violation(s) found."
if [ "$STRICT" = "true" ]; then
echo "::error title=repo-standards::strict mode — failing on $violations violation(s)."
exit 1
fi
echo "repo-standards: warn mode — not failing the build. Set strict:true to enforce."
exit 0
17 changes: 16 additions & 1 deletion .github/workflows/required-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,27 @@ concurrency:
cancel-in-progress: true

jobs:
# Dogfood the org template/standards check on this repo itself.
repo-standards:
name: Repo standards
uses: ./.github/workflows/repo-standards.yml

required:
name: required
needs: [repo-standards]
if: always()
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2
with:
egress-policy: audit
- run: echo "ok — .github repo has no language CI to gate on"
- name: Evaluate upstream results
env:
REPO_STANDARDS_RESULT: ${{ needs.repo-standards.result }}
run: |
set -eu
case "$REPO_STANDARDS_RESULT" in
success|skipped|"") echo "ok — .github repo has no language CI to gate on" ;;
*) echo "::error::repo-standards returned: $REPO_STANDARDS_RESULT"; exit 1 ;;
esac
21 changes: 19 additions & 2 deletions .github/workflows/required.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ on:
type: string
required: false
default: ""
repo-standards-strict:
description: >
Fail `required` when the repo violates template/standards
conformance (LICENSE present, non-stub README, no leftover
{{PLACEHOLDER}} tokens). Default warns only — flip to true once
the repo is clean.
type: boolean
required: false
default: false

permissions:
contents: read
Expand Down Expand Up @@ -198,10 +207,17 @@ jobs:
source-dir: ${{ inputs.cpp-source-dir }}
cmake-flags: ${{ inputs.cpp-cmake-flags }}

# Template/standards conformance — lang-independent, runs for every repo.
repo-standards:
name: Repo standards
uses: ./.github/workflows/repo-standards.yml
with:
strict: ${{ inputs.repo-standards-strict }}

# `required` is the single status-check context consumed by Ruleset A.
required:
name: required
needs: [validate-lang, security, rust, python, node, dotnet, cpp]
needs: [validate-lang, security, rust, python, node, dotnet, cpp, repo-standards]
if: always()
runs-on: ubuntu-latest
steps:
Expand All @@ -218,6 +234,7 @@ jobs:
NODE_RESULT: ${{ needs.node.result }}
DOTNET_RESULT: ${{ needs.dotnet.result }}
CPP_RESULT: ${{ needs.cpp.result }}
REPO_STANDARDS_RESULT: ${{ needs.repo-standards.result }}
run: |
set -eu
# validate-lang must be success — typo defenses handled there
Expand All @@ -226,7 +243,7 @@ jobs:
exit 1
fi
fail=0
for r in "$SECURITY_RESULT" "$RUST_RESULT" "$PYTHON_RESULT" "$NODE_RESULT" "$DOTNET_RESULT" "$CPP_RESULT"; do
for r in "$SECURITY_RESULT" "$RUST_RESULT" "$PYTHON_RESULT" "$NODE_RESULT" "$DOTNET_RESULT" "$CPP_RESULT" "$REPO_STANDARDS_RESULT"; do
case "$r" in
success|skipped|"") ;;
*) echo "::error::Upstream job returned: $r"; fail=1 ;;
Expand Down
Loading
Loading