-
Notifications
You must be signed in to change notification settings - Fork 0
ci: enforce conventional commits and semantic versioning #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
96bfcaf
18ec4de
878a67d
fad2599
52ae121
00ea5b3
60d1a3a
2bd274c
cdbe90d
641cc21
92ced28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,12 @@ name: Validate ci-fleet prototype | |
|
|
||
| on: | ||
| pull_request: | ||
| # `edited` revalidates the title when a contributor retitles the PR: the | ||
| # required check would otherwise stay green for the same head SHA. | ||
| types: [opened, synchronize, reopened, edited] | ||
| push: | ||
| branches: [main] | ||
| tags: ['**'] | ||
|
Nickfost marked this conversation as resolved.
Nickfost marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For an invalid or incorrectly placed version tag, this workflow starts only after the tag ref has already been created, and the workflow has read-only permissions with no step that removes a rejected tag. A malformed or mis-versioned tag therefore remains publicly fetchable even when this check fails, so the stated tag-based release gate does not prevent publication; restrict tag creation to a protected release path that runs these checks before pushing the ref. Useful? React with 👍 / 👎.
Nickfost marked this conversation as resolved.
|
||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
|
|
@@ -14,8 +18,95 @@ concurrency: | |
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| commit-convention: | ||
| name: Enforce conventional commits and pull-request title | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| steps: | ||
| - name: Check out repository | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | ||
| with: | ||
| persist-credentials: false | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Validate pull-request title | ||
| if: ${{ github.event_name == 'pull_request' }} | ||
| env: | ||
| PR_TITLE: ${{ github.event.pull_request.title }} | ||
| run: | | ||
| # The convention checks must run trusted code: a PR can edit its own | ||
| # copy of the validator, so extract it from the base revision when it | ||
|
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a pull request modifies AGENTS.md reference: AGENTS.md:L40-L40 Useful? React with 👍 / 👎. |
||
| # exists there. This first PR bootstraps the gate before the script | ||
| # exists on main; until then the checkout copy is the only available | ||
| # implementation, and this same commit lands it so the next merge | ||
| # base contains it and later PRs are fully trusted. | ||
| validator="$RUNNER_TEMP/trusted-validator.py" | ||
| BASE_SHA="${{ github.event.pull_request.base.sha }}" | ||
| if git cat-file -e "$BASE_SHA:scripts/validate_commits.py" 2>/dev/null; then | ||
| git show "$BASE_SHA:scripts/validate_commits.py" >"$validator" | ||
| else | ||
| cp scripts/validate_commits.py "$validator" | ||
| fi | ||
| python3 "$validator" --pr-title "$PR_TITLE" | ||
|
|
||
| - name: Validate proposed commit messages | ||
| env: | ||
| EVENT_NAME: ${{ github.event_name }} | ||
| BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'push' && github.event.before || '' }} | ||
|
Nickfost marked this conversation as resolved.
|
||
| HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| run: | | ||
| # Validate only commits proposed on this ref (base..head). Commits | ||
| # already on the base branch are never re-checked. The validator is | ||
| # shallow-clone safe: an empty base or empty range falls back to the | ||
| # head commit alone. For workflow_dispatch, validate only HEAD. | ||
| # | ||
| # Like the secret scanner below, the validator itself is extracted | ||
| # from the trusted base revision when available, so a PR cannot pass | ||
| # these gates by editing its own copy of the script. Until the script | ||
| # exists on the base branch (bootstrap PR), the checkout copy runs. | ||
| validator="$RUNNER_TEMP/trusted-validator.py" | ||
| if [[ "$EVENT_NAME" == pull_request ]] && git cat-file -e "$BASE_SHA:scripts/validate_commits.py" 2>/dev/null; then | ||
| git show "$BASE_SHA:scripts/validate_commits.py" >"$validator" | ||
| else | ||
| cp scripts/validate_commits.py "$validator" | ||
|
Nickfost marked this conversation as resolved.
Outdated
|
||
| fi | ||
| # A newly created tag reports an all-zero `before`, which makes | ||
| # `git rev-list <zero>..<tag>` fail and silently reduce validation | ||
| # to the tagged commit alone. Derive the real range start from the | ||
| # merge base with origin/main so every commit behind a branch-local | ||
| # (prerelease) tag is validated too. workflow_dispatch keeps its | ||
| # intentional HEAD-only behavior. | ||
| if [[ "$EVENT_NAME" == push && ( -z "$BASE_SHA" || "$BASE_SHA" =~ ^0+$ ) ]]; then | ||
| BASE_SHA="$(git merge-base "$HEAD_SHA" origin/main)" | ||
| fi | ||
| python3 "$validator" --base "$BASE_SHA" --head "$HEAD_SHA" | ||
|
|
||
| - name: Validate release tags are SemVer 2.0.0 | ||
| if: ${{ startsWith(github.ref, 'refs/tags/') }} | ||
| env: | ||
| TAG_NAME: ${{ github.ref_name }} | ||
| TAG_COMMIT: ${{ github.sha }} | ||
| run: | | ||
| # Like the commit gates above, tag policy must run trusted code: a | ||
| # branch-local tagged commit could otherwise weaken its own tag | ||
| # validation by editing scripts/validate_commits.py. Extract the | ||
| # validator from the merge base with origin/main; fall back to the | ||
| # checkout copy only while the bootstrap PR has no main copy yet. | ||
| validator="$RUNNER_TEMP/trusted-validator.py" | ||
| TRUSTED_SHA="$(git merge-base "$GITHUB_SHA" origin/main)" | ||
| if git cat-file -e "$TRUSTED_SHA:scripts/validate_commits.py" 2>/dev/null; then | ||
| git show "$TRUSTED_SHA:scripts/validate_commits.py" >"$validator" | ||
| else | ||
| cp scripts/validate_commits.py "$validator" | ||
| fi | ||
| # Stable tags must point into the main line (fetch-depth 0 gives us | ||
| # origin/main); prerelease tags must NOT point into it (they are | ||
| # branch-local per docs/CONTRIBUTING.md). | ||
| python3 "$validator" --version "$TAG_NAME" --tag-commit "$TAG_COMMIT" | ||
|
Nickfost marked this conversation as resolved.
Outdated
|
||
|
|
||
| validate: | ||
| name: Build without registering a runner | ||
| needs: commit-convention | ||
|
Nickfost marked this conversation as resolved.
Nickfost marked this conversation as resolved.
|
||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| steps: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| # Contributing to ci-fleet | ||
|
|
||
| This document defines the contributor contract that governs every commit and | ||
| pull request in `RandomDevelopment/ci-fleet`. It is mandatory for all authors | ||
| and agents editing this repository. | ||
|
|
||
| ## Release model | ||
|
|
||
| - **Versioning**: [Semantic Versioning 2.0.0](https://semver.org/). Tags may use | ||
| an optional leading `v` (e.g. `v1.2.3`), but the version payload is always a | ||
| valid SemVer string. | ||
| - **Commit format**: [Conventional Commits 1.0.0](https://www.conventionalcommits.org/). | ||
| The project squash-merges to `main`, so the squash title is the canonical | ||
| release entry and the PR title must itself be a conventional subject. | ||
| - **Pre-1.0 / `0.y.z`**: the project is in initial development. A `0.y.z` | ||
| release has an unstable public API: anything may change at any time without | ||
| notice. PATCH is still permitted for pure internal fixes, but MINOR and | ||
| MAJOR carry no stability guarantee until a `1.0.0` is tagged. Do not invent | ||
| or publish a release merely to satisfy versioning rules; releases are gated | ||
| by `docs/CONTRIBUTING.md` and the operator review window below. | ||
| - **No force-push** of published history and no rebased rewrites of shared | ||
| branches. Use `git revert` for corrections. | ||
|
|
||
| ## Conventional Commits 1.0.0 | ||
|
|
||
| ``` | ||
| <type>[optional scope][!]: <description> | ||
| ``` | ||
|
|
||
| - The `<type>` MUST be one of: `build`, `chore`, `ci`, `docs`, `feat`, `fix`, | ||
| `perf`, `refactor`, `revert`, `style`, `test`. | ||
| - The `<scope>` is optional and nested in parentheses, e.g. `feat(runner):`. | ||
| - Append `!` before the colon to mark a breaking change. | ||
| - The `<description>` is a single line; the complete subject (type, scope, | ||
| marker, separator, and description) is limited to <=100 characters and the | ||
| description begins with a lowercase letter (lowercase ASCII type + scope is | ||
| the convention; the subject itself may contain capitals for identifiers). | ||
| - Separate the subject from the body with exactly one blank line. | ||
| - Footers use `Token: value` form. A breaking change MAY also be declared with | ||
| a `BREAKING CHANGE:` footer (uppercase, per spec). | ||
|
|
||
| ### SemVer mapping | ||
|
|
||
| | Commit | Bump | | ||
| | --- | --- | | ||
| | `fix:` or `perf:`, `refactor:`, `chore:`, `ci:`, `build:`, `style:`, `test:`, `docs:` (no `!`) | PATCH | | ||
| | `feat:` (no `!`) | MINOR | | ||
| | `feat!:`, `fix!:`, any `!`, or `BREAKING CHANGE:` footer | MAJOR | | ||
|
|
||
| ### Examples | ||
|
|
||
| ``` | ||
| feat: add capacity telemetry endpoint | ||
| fix(runner): close leak on job cancellation | ||
| docs: record five-minute CI shard contract | ||
| ci: enforce conventional commits and semantic versioning | ||
| perf: cache host capability lookup | ||
| refactor: de-duplicate reconcile drift detection | ||
| feat!: replace the legacy controller entrypoint | ||
| ``` | ||
|
|
||
| ``` | ||
| fix(ci): stop recommending mutable image tags | ||
|
|
||
| The previous guidance used `:latest`, which violates pinning requirements. | ||
|
|
||
| Closes #42 | ||
| Reviewed-by: An Operator <an-operator@example.org> | ||
| ``` | ||
|
|
||
| ``` | ||
| feat(controller): drop the legacy reconcile command | ||
|
|
||
| BREAKING CHANGE: `install-worker-controller.sh --reconcile` is removed. | ||
| Operators must use `--upgrade` instead. | ||
| ``` | ||
|
|
||
| ## Public API / compatibility contract | ||
|
|
||
| The versioned public API of ci-fleet consists of the following stable | ||
| interfaces. A breaking change to any entry increments the MAJOR version. | ||
|
|
||
| 1. **Configuration schema** — `templates/config-repository/fleet.schema.json`, | ||
| `schema_version: 3`. Managed projects submit Git-authored desired state | ||
| validated against this schema. | ||
| 2. **Task-plan schema** — `examples/project/scripts/ci/plan.schema.json`, | ||
| `schema_version: 1`. The matrix-expansion contract consumed by project | ||
| workflows. | ||
| 3. **Status-report evidence format** — `schemas/status-report-v1.json`, | ||
| `schema_version: 1`. The format emitted by `scripts/status_receiver.py` and | ||
| consumed by health/monitoring tooling. | ||
| 4. **Engine rollout evidence format** — | ||
| `templates/config-repository/engine-rollout-evidence.json`, | ||
| `schema_version: 1`. | ||
| 5. **Installer command contract** — | ||
| `scripts/install-worker-controller.sh` with `--install`, `--adopt`, | ||
| `--check`, `--upgrade`, `--rollback`, `--uninstall` and the | ||
| `--config-repo`, `--ref`, `--controller` arguments. | ||
| 6. **Host-role command contracts** — the systemd unit command lines under | ||
| `host/systemd/*`, including the cleanup, health, drift, and reconcile | ||
| timer/entry-point contracts. | ||
| 7. **Generated task matrix** — the `include` output produced by | ||
| `.github/actions/plan/plan.py`, consumed by project workflow matrices. | ||
|
|
||
| Non-API commits (docs, tests, CI, chore, style) never bump the public version | ||
| for API purposes; CI enforces PATCH-level change at minimum. | ||
|
|
||
| ## Release gate | ||
|
|
||
| A version is released (tagged on `main`) only when: | ||
|
|
||
| - the tagged commit passes all CI checks; | ||
| - the change set is reviewed and the SemVer bump matches the Conventional | ||
| Commits classification; | ||
| - an operator has confirmed the live pilot evidence for any engine rollout | ||
| evidence schema change. | ||
|
|
||
| Do not tag a release to force a version number. This repository is pre-1.0; | ||
| avoid `1.0.0` until the controlled migration and compliance checklist | ||
| (`docs/COMPLIANCE-CHECKLIST.md`) are complete. | ||
|
|
||
| ## Prerelease and build metadata | ||
|
|
||
| - Prerelease identifiers are supported by the validator but are not used for | ||
| `main`-sourced tags. Use `.0` patch sequences or branch-local tags only. | ||
| - Build metadata (`+build.<source>`) is permitted on tags but MUST NOT affect | ||
| SemVer precedence ordering. | ||
|
|
||
| ## Validation | ||
|
|
||
| `scripts/validate_commits.py` enforces the Conventional Commits grammar, the | ||
| SemVer validator, and a `--suggest-bump` helper. `scripts/test_validate_commits.py` | ||
| is the regression suite. Both run in CI (see the `commit-convention` job in | ||
| `.github/workflows/validate.yml`). | ||
|
|
||
| Run locally: | ||
|
|
||
| ```bash | ||
| python3 scripts/test_validate_commits.py | ||
| python3 scripts/validate_commits.py --message - <<< 'feat: local example' | ||
| python3 scripts/validate_commits.py --version 0.1.0 | ||
| ``` |
Uh oh!
There was an error while loading. Please reload this page.