@keelcore/standards 0.9.7 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/governance/ci.md CHANGED
@@ -2,14 +2,12 @@
2
2
 
3
3
  These rules govern all CI workflow and script work. They are non-negotiable.
4
4
 
5
- **Maturity:** Required
6
- **Version:** 1.0.0
7
- **Last Reviewed:** 2026-03-09
5
+ **Maturity:** Required **Version:** 1.1.0 **Last Reviewed:** 2026-10-30
8
6
 
9
7
  ## Core Principle
10
8
 
11
- CI workflow YAML is thin orchestration only. No build, test, lint, packaging, signing, or release logic lives
12
- in YAML — it lives in `scripts/` entrypoints.
9
+ CI workflow YAML is thin orchestration only. No build, test, lint, packaging, signing, or release logic lives in YAML —
10
+ it lives in `scripts/` entrypoints.
13
11
 
14
12
  ## Workflow File Responsibilities
15
13
 
@@ -32,8 +30,8 @@ YAML may only contain:
32
30
 
33
31
  ## Script Entrypoints
34
32
 
35
- One script per concern. Scripts are the canonical entry point for all CI logic. Workflow YAML invokes them;
36
- it does not duplicate their logic.
33
+ One script per concern. Scripts are the canonical entry point for all CI logic. Workflow YAML invokes them; it does not
34
+ duplicate their logic.
37
35
 
38
36
  ## Makefile Entrypoints
39
37
 
@@ -43,59 +41,72 @@ it does not duplicate their logic.
43
41
  - Multi-command steps must be aggregated into one target; YAML calls the target.
44
42
  - Setup steps (tooling, fixtures) are also Makefile targets (e.g. `make setup-bats`).
45
43
  - The Makefile is the single dev-facing interface: what CI calls, a developer can call locally.
46
- - Every `scripts/**/*.sh` file (except sourced library files under `scripts/lib/`) MUST have a
47
- Makefile target that invokes it — the script path must appear in a Makefile recipe.
44
+ - Every `scripts/**/*.sh` file (except sourced library files under `scripts/lib/`) MUST have a Makefile target that
45
+ invokes it — the script path must appear in a Makefile recipe.
46
+ - Every Makefile target MUST have at most **one active recipe line**, and that line MUST invoke a single
47
+ `scripts/**/*.sh` script. "Active" excludes blank lines, `#` comments, and `@echo` lines used for human-readable
48
+ progress reporting. Concretely:
49
+ - Allowed: any number of `@echo` lines, followed by exactly one `bash scripts/X/Y.sh ...` line.
50
+ - Allowed: prerequisite chains in the target header (`mytarget: prereq1 prereq2`) — the rule applies to the recipe
51
+ body, not to prerequisite expansion.
52
+ - Disallowed: two or more substantive commands in one recipe (e.g. `cargo clean` followed by `rm -f data/...`).
53
+ Aggregate them into a single shell script and call it.
54
+ - Disallowed: invoking `cargo`, `go`, `bats`, `ln`, `mkdir`, `tee`, or any tool directly from the Makefile recipe.
55
+ Wrap the call in a `scripts/` shell script and have the Makefile invoke the script.
56
+ - Rationale: the Makefile is a router, not an orchestrator. All multi-step orchestration, flag handling, env var
57
+ assembly, and tool invocation belongs in `scripts/`, which is runnable identically by humans, by `make`, and by CI
58
+ YAML. This eliminates Makefile quoting traps, makes recipes shellcheck-able, and means every project concern has
59
+ exactly one canonical implementation surface.
48
60
 
49
61
  ## Universal Canonical Makefile Targets
50
62
 
51
- Every project MUST define these language-independent targets. They form a stable, tool-agnostic
52
- interface for humans and CI alike. Names are fixed; projects may not rename or skip them.
53
- Language-specific subtargets (e.g. `make lint-go`, `make test-unit`) are additive — they do not
54
- replace the universal targets.
63
+ Every project MUST define these language-independent targets. They form a stable, tool-agnostic interface for humans and
64
+ CI alike. Names are fixed; projects may not rename or skip them. Language-specific subtargets (e.g. `make lint-go`,
65
+ `make test-unit`) are additive — they do not replace the universal targets.
55
66
 
56
67
  ### Always required
57
68
 
58
- | Target | Makefile target | Script | Responsibility |
59
- |---|---|---|---|
60
- | Build default artifact | `make build` | project-specific | Produce the primary deliverable (binary, image, package). |
61
- | Run all linters | `make lint` | project-specific | Format check + static analysis. Must invoke `make lint-newlines`. |
62
- | Run full test suite | `make test` | project-specific | Unit + integration tests in sequence. |
63
- | Run unit tests | `make unit-test` | `scripts/test/ci.sh` | Unit tests only; produces JUnit XML and coverage profile. |
64
- | Run integration tests | `make integration-test` | project-specific | BATS / end-to-end tests against a built artifact. |
65
- | Remove artifacts | `make clean` | — | Remove `dist/` and generated files. |
66
- | Standards audit | `make audit` | `scripts/ci/audit-make-targets.sh` | CI/Makefile compliance auditor. |
67
- | Code coverage report | `make coverage` | `scripts/test/coverage.sh` | Print total %, uncovered statements, total lines. |
68
- | PR coverage gate | `make ci-coverage-delta` | `scripts/test/coverage-delta.sh` | Fail PR if coverage drops > threshold vs. base branch. |
69
- | PR policy gate | `make ci-pr-policy` | `scripts/ci/pr-policy.sh` | Title, body, branch naming, linked issue enforcement. |
70
- | Secret scan | `make ci-secret-scan` | `scripts/ci/secret-scan.sh` | gitleaks scan; required on every PR and default-branch push. |
71
- | DCO check | `make ci-dco` | `scripts/ci/dco-check.sh` | Signed-off-by trailer on every commit in the PR. |
72
- | Trailing newlines | `make lint-newlines` | `scripts/lint/newlines.sh` | Every `.md`, `.sh`, `.go` ends with `\n`. |
73
- | Legal drift check | `make check-legal-drift` | `scripts/check-legal-drift.sh` | Copied legal files (LICENSE, TRADEMARK) match the source of truth. |
69
+ | Target | Makefile target | Script | Responsibility |
70
+ | ---------------------- | ------------------------ | ---------------------------------- | ------------------------------------------------------------------ |
71
+ | Build default artifact | `make build` | project-specific | Produce the primary deliverable (binary, image, package). |
72
+ | Run all linters | `make lint` | project-specific | Format check + static analysis. Must invoke `make lint-newlines`. |
73
+ | Run full test suite | `make test` | project-specific | Unit + integration tests in sequence. |
74
+ | Run unit tests | `make unit-test` | `scripts/test/ci.sh` | Unit tests only; produces JUnit XML and coverage profile. |
75
+ | Run integration tests | `make integration-test` | project-specific | BATS / end-to-end tests against a built artifact. |
76
+ | Remove artifacts | `make clean` | — | Remove `dist/` and generated files. |
77
+ | Standards audit | `make audit` | `scripts/ci/audit-make-targets.sh` | CI/Makefile compliance auditor. |
78
+ | Code coverage report | `make coverage` | `scripts/test/coverage.sh` | Print total %, uncovered statements, total lines. |
79
+ | PR coverage gate | `make ci-coverage-delta` | `scripts/test/coverage-delta.sh` | Fail PR if coverage drops > threshold vs. base branch. |
80
+ | PR policy gate | `make ci-pr-policy` | `scripts/ci/pr-policy.sh` | Title, body, branch naming, linked issue enforcement. |
81
+ | Secret scan | `make ci-secret-scan` | `scripts/ci/secret-scan.sh` | gitleaks scan; required on every PR and default-branch push. |
82
+ | DCO check | `make ci-dco` | `scripts/ci/dco-check.sh` | Signed-off-by trailer on every commit in the PR. |
83
+ | Trailing newlines | `make lint-newlines` | `scripts/lint/newlines.sh` | Every `.md`, `.sh`, `.go` ends with `\n`. |
84
+ | Legal drift check | `make check-legal-drift` | `scripts/check-legal-drift.sh` | Copied legal files (LICENSE, TRADEMARK) match the source of truth. |
74
85
 
75
86
  ### Required for projects with releases
76
87
 
77
- | Target | Makefile target | Script | Responsibility |
78
- |---|---|---|---|
79
- | Generate checksums | `make release-checksums` | `scripts/release/checksums.sh` | Write `dist/SHA256SUMS` for all release artifacts. |
80
- | Verify checksums | `make release-checksums-verify` | `scripts/release/checksums.sh --verify` | Confirm downloaded artifacts match `SHA256SUMS`. |
81
- | Generate SBOM | `make release-sbom` | `scripts/release/sbom.sh` | SPDX JSON SBOM via syft; attached to every release. |
82
- | Sign artifacts | `make release-sign` | `scripts/release/sign.sh` | cosign keyless signing; bundles attached alongside binaries. |
83
- | Upload to release | `make release-upload` | `scripts/release/upload.sh` | Push artifacts + checksums + SBOM to GitHub Release. |
84
- | Version tagging | `make create-release` | `scripts/release/create-release.sh` | Schema-diff semver bump + annotated tag; developer action only. |
88
+ | Target | Makefile target | Script | Responsibility |
89
+ | ------------------ | ------------------------------- | --------------------------------------- | --------------------------------------------------------------- |
90
+ | Generate checksums | `make release-checksums` | `scripts/release/checksums.sh` | Write `dist/SHA256SUMS` for all release artifacts. |
91
+ | Verify checksums | `make release-checksums-verify` | `scripts/release/checksums.sh --verify` | Confirm downloaded artifacts match `SHA256SUMS`. |
92
+ | Generate SBOM | `make release-sbom` | `scripts/release/sbom.sh` | SPDX JSON SBOM via syft; attached to every release. |
93
+ | Sign artifacts | `make release-sign` | `scripts/release/sign.sh` | cosign keyless signing; bundles attached alongside binaries. |
94
+ | Upload to release | `make release-upload` | `scripts/release/upload.sh` | Push artifacts + checksums + SBOM to GitHub Release. |
95
+ | Version tagging | `make create-release` | `scripts/release/create-release.sh` | Semver bump from project hook + signed tag; developer action. |
85
96
 
86
97
  ### Required for projects with container images
87
98
 
88
- | Target | Makefile target | Script | Responsibility |
89
- |---|---|---|---|
99
+ | Target | Makefile target | Script | Responsibility |
100
+ | -------------------- | --------------------- | --------------------------- | --------------------------------------------------------- |
90
101
  | Build and push image | `make release-docker` | `scripts/release/docker.sh` | Build, tag, push, and sign container images from `dist/`. |
91
102
 
92
103
  ### Required for projects with Helm charts
93
104
 
94
- | Target | Makefile target | Script | Responsibility |
95
- |---|---|---|---|
96
- | Helm lint | `make lint-helm` | `scripts/lint/helm.sh` | `helm lint` + template render; no cluster required. |
97
- | Helm schema validation | `make lint-helm-validate` | `scripts/lint/helm-validate.sh` | kubeconform validation of rendered templates. |
98
- | Helm chart publish | `make release-helm-push` | `scripts/release/helm-push.sh` | OCI push to GHCR + cosign signing of the chart digest. |
105
+ | Target | Makefile target | Script | Responsibility |
106
+ | ---------------------- | ------------------------- | ------------------------------- | ------------------------------------------------------ |
107
+ | Helm lint | `make lint-helm` | `scripts/lint/helm.sh` | `helm lint` + template render; no cluster required. |
108
+ | Helm schema validation | `make lint-helm-validate` | `scripts/lint/helm-validate.sh` | kubeconform validation of rendered templates. |
109
+ | Helm chart publish | `make release-helm-push` | `scripts/release/helm-push.sh` | OCI push to GHCR + cosign signing of the chart digest. |
99
110
 
100
111
  ## CI Auditor
101
112
 
@@ -106,15 +117,14 @@ replace the universal targets.
106
117
  3. All universal canonical targets exist in the Makefile.
107
118
  - `make audit` invokes the auditor.
108
119
  - The auditor MUST be a required CI step (add to the workflow as `run: make audit`).
109
- - The auditor MUST be wired into the pre-commit hook, gated on changes to
110
- `.github/workflows/`, `scripts/`, or `Makefile`.
120
+ - The auditor MUST be wired into the pre-commit hook, gated on changes to `.github/workflows/`, `scripts/`, or
121
+ `Makefile`.
111
122
 
112
123
  ### Canonical Script Source of Truth
113
124
 
114
- The source-of-truth copies of all canonical scripts live in the `keelcore/standards` repository
115
- (`scripts/` directory). Downstream repos MUST keep their copies byte-identical to the standards
116
- versions. Any change to a canonical script must be made first in `keelcore/standards` and then
117
- propagated to all downstream repos.
125
+ The source-of-truth copies of all canonical scripts live in the `keelcore/standards` repository (`scripts/` directory).
126
+ Downstream repos MUST keep their copies byte-identical to the standards versions. Any change to a canonical script must
127
+ be made first in `keelcore/standards` and then propagated to all downstream repos.
118
128
 
119
129
  To verify that a downstream repo's canonical scripts match the standards source of truth, run:
120
130
 
@@ -122,60 +132,67 @@ To verify that a downstream repo's canonical scripts match the standards source
122
132
  make verify-canonical REPO=<path-to-downstream-repo-root>
123
133
  ```
124
134
 
125
- from the `keelcore/standards` repository root. This compares `sha256sum` of each mandatory script
126
- and reports PASS/FAIL per file, exiting 1 if any mismatch is found.
135
+ from the `keelcore/standards` repository root. This compares `sha256sum` of each mandatory script and reports PASS/FAIL
136
+ per file, exiting 1 if any mismatch is found.
137
+
138
+ Project-local hooks (`scripts/**/*.local.sh`) are owned by the repo that holds them and are never canonical, including
139
+ the ones in `keelcore/standards` itself. `verify-canonical` and `governance-refresh` neither compare nor sync them, so a
140
+ consumer's hook is never overwritten. A canonical script that calls a hook resolves it from the git toplevel of the repo
141
+ it runs in: inside a consumer it calls the consumer's hook, never the copy in `.standards`.
127
142
 
128
143
  ## Canonical CI Scripts
129
144
 
130
- Scripts whose names and responsibilities are fixed across all projects. Projects must not rename
131
- these scripts or move their canonical logic elsewhere.
145
+ Scripts whose names and responsibilities are fixed across all projects. Projects must not rename these scripts or move
146
+ their canonical logic elsewhere.
132
147
 
133
- The source of truth for all canonical scripts is the `keelcore/standards` repository. Downstream
134
- repo copies must be byte-identical to the standards versions for the scripts marked **canonical**.
135
- Use `make verify-canonical REPO=<path>` from the standards repo to verify compliance.
148
+ The source of truth for all canonical scripts is the `keelcore/standards` repository. Downstream repo copies must be
149
+ byte-identical to the standards versions for the scripts marked **canonical**. Use `make verify-canonical REPO=<path>`
150
+ from the standards repo to verify compliance.
136
151
 
137
152
  ### Always required
138
153
 
139
- | Script | Makefile target | Responsibility | Source of truth |
140
- |---|---|---|---|
141
- | `scripts/ci/audit-make-targets.sh` | `make audit` | CI/Makefile standards compliance auditor. | keelcore/standards |
142
- | `scripts/ci/pr-policy.sh` | `make ci-pr-policy` | PR policy gate: title, body, branch naming, linked issue. | project-specific |
143
- | `scripts/ci/secret-scan.sh` | `make ci-secret-scan` | Secret scanning on every PR and default-branch push. | keelcore/standards |
144
- | `scripts/ci/dco-check.sh` | `make ci-dco` | DCO Signed-off-by trailer verification. | keelcore/standards |
145
- | `scripts/lint/newlines.sh` | `make lint-newlines` | Trailing newline enforcement for `.md`, `.sh`, `.go` files. | keelcore/standards |
146
- | `scripts/test/coverage.sh` | `make coverage` | Coverage report: total %, uncovered statements, total lines. | keelcore/standards |
147
- | `scripts/test/coverage-delta.sh` | `make ci-coverage-delta` | PR coverage delta gate (fails if drop exceeds threshold). | keelcore/standards |
148
- | `scripts/check-legal-drift.sh` | `make check-legal-drift` | Verify copied legal files match the source of truth. | project-specific |
149
- | `scripts/lib/paths.sh` | _(sourced; no target)_ | Shared path-filter helpers; sourced by other scripts. | keelcore/standards |
154
+ | Script | Makefile target | Responsibility | Source of truth |
155
+ | ---------------------------------- | ------------------------ | ------------------------------------------------------------------ | ------------------ |
156
+ | `scripts/ci/audit-make-targets.sh` | `make audit` | CI/Makefile standards compliance auditor. | keelcore/standards |
157
+ | `scripts/ci/pr-policy.sh` | `make ci-pr-policy` | PR policy gate: title, body, branch naming, linked issue. | project-specific |
158
+ | `scripts/ci/secret-scan.sh` | `make ci-secret-scan` | Secret scanning on every PR and default-branch push. | keelcore/standards |
159
+ | `scripts/ci/dco-check.sh` | `make ci-dco` | DCO Signed-off-by trailer verification. | keelcore/standards |
160
+ | `scripts/ci/setup-bats.sh` | `make setup-bats` | Install bats-core (test harness) idempotently across darwin/linux. | keelcore/standards |
161
+ | `scripts/install-hooks.sh` | `make install-hooks` | Symlink `scripts/git_precommit.sh` into `.git/hooks/pre-commit`. | keelcore/standards |
162
+ | `scripts/lint/newlines.sh` | `make lint-newlines` | Trailing newline enforcement for `.md`, `.sh`, `.go` files. | keelcore/standards |
163
+ | `scripts/test/coverage.sh` | `make coverage` | Coverage report: total %, uncovered statements, total lines. | keelcore/standards |
164
+ | `scripts/test/coverage-delta.sh` | `make ci-coverage-delta` | PR coverage delta gate (fails if drop exceeds threshold). | keelcore/standards |
165
+ | `scripts/check-legal-drift.sh` | `make check-legal-drift` | Verify copied legal files match the source of truth. | project-specific |
166
+ | `scripts/lib/paths.sh` | _(sourced; no target)_ | Shared path-filter helpers; sourced by other scripts. | keelcore/standards |
150
167
 
151
168
  ### Required for projects with releases
152
169
 
153
- | Script | Makefile target | Responsibility |
154
- |---|---|---|
155
- | `scripts/release/checksums.sh` | `make release-checksums` / `make release-checksums-verify` | Generate or verify `dist/SHA256SUMS`. |
156
- | `scripts/release/sbom.sh` | `make release-sbom` | Generate SPDX JSON SBOM via syft. |
157
- | `scripts/release/sign.sh` | `make release-sign` | cosign keyless signing of all release artifacts. |
158
- | `scripts/release/upload.sh` | `make release-upload` | Upload artifacts to GitHub Release via gh CLI. |
159
- | `scripts/release/create-release.sh` | `make create-release` | Schema-diff semver version computation + tag; developer action. |
170
+ | Script | Makefile target | Responsibility |
171
+ | ----------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------- |
172
+ | `scripts/release/checksums.sh` | `make release-checksums` / `make release-checksums-verify` | Generate or verify `dist/SHA256SUMS`. |
173
+ | `scripts/release/sbom.sh` | `make release-sbom` | Generate SPDX JSON SBOM via syft. |
174
+ | `scripts/release/sign.sh` | `make release-sign` | cosign keyless signing of all release artifacts. |
175
+ | `scripts/release/upload.sh` | `make release-upload` | Upload artifacts to GitHub Release via gh CLI. |
176
+ | `scripts/release/create-release.sh` | `make create-release` | Semver bump from project hook + signed tag; developer action. |
160
177
 
161
178
  ### Required for projects with container images
162
179
 
163
- | Script | Makefile target | Responsibility |
164
- |---|---|---|
180
+ | Script | Makefile target | Responsibility |
181
+ | --------------------------- | --------------------- | -------------------------------------------- |
165
182
  | `scripts/release/docker.sh` | `make release-docker` | Build, tag, push, and sign container images. |
166
183
 
167
184
  ### Required for projects with Helm charts
168
185
 
169
- | Script | Makefile target | Responsibility |
170
- |---|---|---|
171
- | `scripts/lint/helm.sh` | `make lint-helm` | `helm lint` + template render; no cluster required. |
186
+ | Script | Makefile target | Responsibility |
187
+ | ------------------------------- | ------------------------- | --------------------------------------------------------- |
188
+ | `scripts/lint/helm.sh` | `make lint-helm` | `helm lint` + template render; no cluster required. |
172
189
  | `scripts/lint/helm-validate.sh` | `make lint-helm-validate` | kubeconform schema validation of rendered Helm templates. |
173
- | `scripts/release/helm-push.sh` | `make release-helm-push` | OCI Helm chart push to GHCR + cosign signing. |
190
+ | `scripts/release/helm-push.sh` | `make release-helm-push` | OCI Helm chart push to GHCR + cosign signing. |
174
191
 
175
192
  ## Source File Formatting Invariants
176
193
 
177
- Every text source file MUST end with a single trailing newline (`\n`). Files without one fail the
178
- pre-commit hook and CI lint. Covered extensions:
194
+ Every text source file MUST end with a single trailing newline (`\n`). Files without one fail the pre-commit hook and CI
195
+ lint. Covered extensions:
179
196
 
180
197
  - Source: `.go`, `.sh`
181
198
  - Markup / documentation: `.md`
@@ -186,24 +203,24 @@ Rules:
186
203
 
187
204
  - Enforce via `scripts/lint/newlines.sh`; auto-fix via `scripts/lint/newlines.sh --fix`.
188
205
  - The pre-commit hook checks staged files; `make lint-newlines` checks the full tracked tree.
189
- - Adding a new text-format type to the repo? Add its glob to `scripts/lint/newlines.sh` and the
190
- pre-commit `staged_text_files` filter in the same commit.
206
+ - Adding a new text-format type to the repo? Add its glob to `scripts/lint/newlines.sh` and the pre-commit
207
+ `staged_text_files` filter in the same commit.
191
208
 
192
209
  ## Build Platform vs. Target Platform
193
210
 
194
211
  - Prefer build-once, deploy/validate-everywhere.
195
212
  - Use cross-compilation to produce artifacts from a single runner.
196
213
  - Use runner matrices for testing/smoke validation on target platforms, not for rebuilding.
197
- - Per-platform builds only when required by native signing, platform-bound linkers, OS-specific packaging,
198
- or target-specific verification unavailable elsewhere. State the reason explicitly when doing so.
214
+ - Per-platform builds only when required by native signing, platform-bound linkers, OS-specific packaging, or
215
+ target-specific verification unavailable elsewhere. State the reason explicitly when doing so.
199
216
  - Artifact promotion over artifact regeneration: the CI-tested artifact is the release artifact.
200
217
 
201
218
  ## Platform-Specific Finishing Steps
202
219
 
203
220
  - Signing, notarization, and native packaging are acceptable reasons for platform matrices.
204
221
  - Build the artifact in a canonical build job; use a platform matrix only for the platform-bound finishing step.
205
- - Keep signing, notarization, and packaging steps in repository scripts; workflow YAML provides only
206
- orchestration, credentials, and artifact movement.
222
+ - Keep signing, notarization, and packaging steps in repository scripts; workflow YAML provides only orchestration,
223
+ credentials, and artifact movement.
207
224
 
208
225
  ## Permissions
209
226
 
@@ -216,8 +233,8 @@ Rules:
216
233
  ## Action Version Pinning
217
234
 
218
235
  - Always pin action versions to a full commit SHA, not a tag. Tags are mutable.
219
- - Correct: `actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2`
220
- - Incorrect: `actions/checkout@v4`
236
+ - Correct: `actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2`
237
+ - Incorrect: `actions/checkout@v4`
221
238
  - Use Dependabot (`dependabot.yml`) with `package-ecosystem: github-actions` to keep SHA pins current.
222
239
 
223
240
  ## Toolchain Version
@@ -361,10 +378,10 @@ Rules:
361
378
 
362
379
  ### CI Workflow
363
380
 
364
- - PR job: compute current coverage → download `coverage/baseline.json` from the base branch →
365
- compare → fail if current < baseline.
366
- - Post-merge job (runs on default branch only): recompute coverage → commit updated
367
- `coverage/baseline.json` → push directly (no PR required for baseline updates).
381
+ - PR job: compute current coverage → download `coverage/baseline.json` from the base branch → compare → fail if current
382
+ < baseline.
383
+ - Post-merge job (runs on default branch only): recompute coverage → commit updated `coverage/baseline.json` → push
384
+ directly (no PR required for baseline updates).
368
385
  - The post-merge baseline update commit must be signed and attributed to the CI bot identity.
369
386
  - Baseline update commits use a conventional commit message: `chore: update coverage baseline to XX.X%`.
370
387
 
@@ -406,75 +423,79 @@ Rules:
406
423
 
407
424
  ### Versioning Specification
408
425
 
409
- All version numbers follow **Semantic Versioning 2.0.0** — [https://semver.org](https://semver.org).
410
- That document is canonical and must not be contradicted by project-specific rules.
411
- Version tags take the form `vMAJOR.MINOR.PATCH` with no pre-release or build-metadata
412
- extensions at this time.
426
+ All version numbers follow **Semantic Versioning 2.0.0** — [https://semver.org](https://semver.org). That document is
427
+ canonical and must not be contradicted by project-specific rules. Version tags take the form `vMAJOR.MINOR.PATCH` with
428
+ no pre-release or build-metadata extensions at this time.
413
429
 
414
430
  Specific semver.org rules in force:
415
431
 
416
- - **[Item 2](https://semver.org/#spec-item-2):** Version numbers take the form
417
- `MAJOR.MINOR.PATCH` — non-negative integers, no leading zeroes.
418
- - **[Item 4](https://semver.org/#spec-item-4):** MAJOR is incremented for incompatible
419
- API changes; MINOR and PATCH reset to 0.
420
- - **[Item 5](https://semver.org/#spec-item-5):** MINOR is incremented for new
421
- backward-compatible functionality; PATCH resets to 0.
422
- - **[Item 6](https://semver.org/#spec-item-6):** PATCH is incremented for
423
- backward-compatible bug fixes only.
424
- - **[Item 9](https://semver.org/#spec-item-9):** Major version zero (`0.y.z`) is for
425
- initial development. The public API is not yet stable and anything may change.
426
- - **[Item 10](https://semver.org/#spec-item-10):** `1.0.0` defines the first stable
427
- public API. Subsequent increments follow the rules above without exception.
432
+ - **[Item 2](https://semver.org/#spec-item-2):** Version numbers take the form `MAJOR.MINOR.PATCH` — non-negative
433
+ integers, no leading zeroes.
434
+ - **[Item 4](https://semver.org/#spec-item-4):** MAJOR is incremented for incompatible API changes; MINOR and PATCH
435
+ reset to 0.
436
+ - **[Item 5](https://semver.org/#spec-item-5):** MINOR is incremented for new backward-compatible functionality; PATCH
437
+ resets to 0.
438
+ - **[Item 6](https://semver.org/#spec-item-6):** PATCH is incremented for backward-compatible bug fixes only.
439
+ - **[Item 9](https://semver.org/#spec-item-9):** Major version zero (`0.y.z`) is for initial development. The public API
440
+ is not yet stable and anything may change.
441
+ - **[Item 10](https://semver.org/#spec-item-10):** `1.0.0` defines the first stable public API. Subsequent increments
442
+ follow the rules above without exception.
428
443
 
429
444
  ### Tagging is a Developer Action — Never CI
430
445
 
431
- - Release version tags (`vX.Y.Z`) are created exclusively by developers running
432
- `scripts/release/create-release.sh` from a local checkout.
446
+ - Release version tags (`vX.Y.Z`) are created exclusively by developers running `scripts/release/create-release.sh` from
447
+ a local checkout.
433
448
  - CI must never create, push, or move version tags under any circumstance.
434
- - This is a hard policy: no workflow file may contain `git tag`, `git push --tags`,
435
- or equivalent tag-creation steps.
436
- - Rationale: a tag is a public commitment about what version of the software a
437
- commit represents. That decision requires human judgment and cannot be delegated
438
- to automation.
449
+ - This is a hard policy: no workflow file may contain `git tag`, `git push --tags`, or equivalent tag-creation steps.
450
+ - Rationale: a tag is a public commitment about what version of the software a commit represents. That decision requires
451
+ human judgment and cannot be delegated to automation.
439
452
 
440
453
  ### Automatic Tag Triggering Is Permitted
441
454
 
442
- - CI workflows _triggered by_ a pushed tag (e.g. `on: push: tags: ['v*']`) are
443
- permitted and expected — they run the release pipeline once a developer has
444
- pushed a tag.
455
+ - CI workflows _triggered by_ a pushed tag (e.g. `on: push: tags: ['v*']`) are permitted and expected — they run the
456
+ release pipeline once a developer has pushed a tag.
445
457
  - The distinction is: CI reacts to tags; CI does not create tags.
446
458
 
447
459
  ### Version Computation
448
460
 
449
- - Bump level (major / minor / patch) is derived from `pkg/config/schema.yaml` by
450
- diffing the field set at the previous tag against the field set at HEAD:
451
- - Removed fields → breaking change → major bump
452
- ([semver.org item 4](https://semver.org/#spec-item-4)).
453
- - Added fields (no removals) → new feature → minor bump
454
- ([semver.org item 5](https://semver.org/#spec-item-5)).
455
- - No field surface change → patch bump
456
- ([semver.org item 6](https://semver.org/#spec-item-6)).
457
- - `scripts/release/create-release.sh` performs this computation, presents
458
- findings to the developer for approval, then creates and pushes the annotated
459
- tag on confirmation.
460
- - `scripts/release/gen-schema.sh` regenerates `pkg/config/schema.yaml` from
461
- `cmd/config-schema/main.go` after any change to the config struct. The
462
- consistency suite enforces that `schema.yaml` is never stale.
461
+ - Git tags are the only source of truth for versions. Projects keep no version file and make no version-bump commit;
462
+ every publisher derives the version from the tag at publish time.
463
+ - `scripts/release/create-release.sh` is canonical (source of truth: `keelcore/standards`). It checks preconditions (on
464
+ `main`, upstream set, `HEAD` equals the upstream after a fetch, clean tree), finds the previous tag, computes the next
465
+ version, presents the findings for approval, then creates a signed annotated tag and pushes that one tag.
466
+ - The bump level comes from the project's own hook, `scripts/release/create-release.local.sh`, resolved from the git
467
+ toplevel of the repo being released. The driver calls it as `create-release.local.sh <previous-tag>`; the hook prints
468
+ two lines to stdout: the bump level (`major`, `minor` or `patch`) and a one-line tag subject. Its stderr is shown to
469
+ the developer and recorded in the tag body; a non-zero exit aborts the release. With no hook, every release is a
470
+ `patch`.
471
+ - Each hook maps its project's public surface to semver:
472
+ - Breaking change → major bump ([semver.org item 4](https://semver.org/#spec-item-4)).
473
+ - New backward-compatible functionality → minor bump ([semver.org item 5](https://semver.org/#spec-item-5)).
474
+ - Anything else → patch bump ([semver.org item 6](https://semver.org/#spec-item-6)).
475
+ - While the major version is 0 ([semver.org item 9](https://semver.org/#spec-item-9)) the driver demotes the bump one
476
+ step: major → minor, minor → patch. A patch stays a patch when there are commits since the previous tag; with none,
477
+ there is nothing to release.
478
+ - `make create-release DRY_RUN=1` (`--dry-run`) computes and prints the version and tag message without prompting,
479
+ tagging or pushing; failed preconditions are reported as warnings.
480
+ - Worked example (keel): the hook diffs the field set of `pkg/config/schema.yaml` at the previous tag against HEAD.
481
+ Removed fields → major; added fields (no removals) → minor; no field surface change → patch.
482
+ `scripts/release/gen-schema.sh` regenerates the schema from `cmd/config-schema/main.go` after any change to the config
483
+ struct, and the consistency suite enforces that `schema.yaml` is never stale.
484
+ - `keelcore/standards`: the hook reads the `## [Unreleased]` section of `CHANGELOG.md`. Any `### Removed` entry → major;
485
+ else any `### Added` or `### Changed` entry → minor; else patch. An empty `[Unreleased]` fails: nothing to release.
463
486
 
464
487
  ### --force Override Rules
465
488
 
466
- Developers may override the computed version with `--force vX.Y.Z`. Only bare
467
- `MAJOR.MINOR.PATCH` versions are accepted; pre-release identifiers (`-alpha.1`)
468
- and build metadata (`+sha`) are not supported by the tooling at this time.
489
+ Developers may override the computed version with `--force vX.Y.Z`. Only bare `MAJOR.MINOR.PATCH` versions are accepted;
490
+ pre-release identifiers (`-alpha.1`) and build metadata (`+sha`) are not supported by the tooling at this time.
469
491
 
470
- - **Pre-1.0 (major = 0):** Only versions in the `0.x.y` range or exactly `v1.0.0`
471
- are accepted. Forcing `v1.1.0` or `v2.0.0` from a `0.x` base is rejected.
492
+ - **Pre-1.0 (major = 0):** Only versions in the `0.x.y` range or exactly `v1.0.0` are accepted. Forcing `v1.1.0` or
493
+ `v2.0.0` from a `0.x` base is rejected.
472
494
  - **Post-1.0 (major ≥ 1):** `--force` is restricted to exact single-step increments:
473
495
  - Patch: `cur_maj.cur_min.(cur_pat+1)` — always allowed.
474
496
  - Minor: `cur_maj.(cur_min+1).0` — always allowed (e.g. internal improvements).
475
- - Major: only accepted when breaking changes are detected **and** the forced
476
- version exactly matches the auto-computed version. `--force` cannot suppress
477
- or redirect a breaking-change bump, and cannot skip versions.
497
+ - Major: only accepted when the hook reports a breaking change (major) **and** the forced version exactly matches the
498
+ auto-computed version. `--force` cannot suppress or redirect a breaking-change bump, and cannot skip versions.
478
499
 
479
500
  ## Do Not
480
501
 
@@ -493,4 +514,4 @@ and build metadata (`+sha`) are not supported by the tooling at this time.
493
514
  - Allow release jobs to run without environment protection and required human reviewers.
494
515
  - Ship release artifacts without provenance attestation, signatures, or checksums.
495
516
  - Create, push, or move version tags from CI. Tags are a developer action only.
496
- - Bypass the `create-release.sh` script to tag manually without the schema-diff approval step.
517
+ - Bypass the `create-release.sh` script to tag manually without its approval step.