@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.
@@ -1,156 +1,150 @@
1
1
  # Platform Architecture Standards
2
2
 
3
- These rules govern platform-level decisions across networking, DNS, TLS, WAF, and traffic management.
4
- They are non-negotiable unless explicitly superseded by a signed ADR.
3
+ These rules govern platform-level decisions across networking, DNS, TLS, WAF, and traffic management. They are
4
+ non-negotiable unless explicitly superseded by a signed ADR.
5
5
 
6
- **Maturity:** Required
7
- **Version:** 1.0.0
8
- **Last Reviewed:** 2026-03-09
6
+ **Maturity:** Required **Version:** 1.0.0 **Last Reviewed:** 2026-03-09
9
7
 
10
8
  ## Governance Process
11
9
 
12
10
  ### Decision-Making (DACI)
13
11
 
14
- 1. All platform architecture decisions use DACI: one Driver, one Approver (Architecture Review Board
15
- chair or delegate), Consulted stakeholders, and Informed parties.
12
+ 1. All platform architecture decisions use DACI: one Driver, one Approver (Architecture Review Board chair or delegate),
13
+ Consulted stakeholders, and Informed parties.
16
14
  2. The Driver authors the RFC and owns shepherding it through review.
17
15
  3. The Approver has final authority; no decision is binding without Approver sign-off.
18
16
  4. Decisions that affect more than one service domain require ARB review before implementation.
19
17
 
20
18
  ### RFC Process
21
19
 
22
- 5. Any change to platform standards, vendor selection, or network topology requires an RFC.
23
- 6. RFC template: problem statement, proposed solution, alternatives considered, rollout plan, rollback plan.
24
- 7. RFC comment period is a minimum of five business days for standard changes; urgent security patches
25
- may compress to 24 hours with ARB chair notification.
26
- 8. Accepted RFCs are archived in `docs/rfc/` with status and the date of acceptance.
20
+ 1. Any change to platform standards, vendor selection, or network topology requires an RFC.
21
+ 2. RFC template: problem statement, proposed solution, alternatives considered, rollout plan, rollback plan.
22
+ 3. RFC comment period is a minimum of five business days for standard changes; urgent security patches may compress to
23
+ 24 hours with ARB chair notification.
24
+ 4. Accepted RFCs are archived in `docs/rfc/` with status and the date of acceptance.
27
25
 
28
26
  ### Architecture Review Board (ARB)
29
27
 
30
- 9. ARB meets on a defined cadence (minimum monthly); decisions and attendance are published to the
31
- engineering wiki within five business days of each meeting.
32
- 10. ARB publishes a versioned Architecture Standards document at least once per quarter.
33
- 11. Previous versions of Architecture Standards are archived and permanently accessible.
28
+ 1. ARB meets on a defined cadence (minimum monthly); decisions and attendance are published to the engineering wiki
29
+ within five business days of each meeting.
30
+ 2. ARB publishes a versioned Architecture Standards document at least once per quarter.
31
+ 3. Previous versions of Architecture Standards are archived and permanently accessible.
34
32
 
35
33
  ## Domain and IP
36
34
 
37
35
  ### Domain Registrar
38
36
 
39
- 12. All domains are registered through the approved corporate registrar (documented in the internal
40
- vendor registry). Personal or team-owned registrar accounts are prohibited for production domains.
41
- 13. Domain transfers require ARB approval and a change record.
42
- 14. WHOIS privacy protection is enabled on all externally registered domains.
37
+ 1. All domains are registered through the approved corporate registrar (documented in the internal vendor registry).
38
+ Personal or team-owned registrar accounts are prohibited for production domains.
39
+ 2. Domain transfers require ARB approval and a change record.
40
+ 3. WHOIS privacy protection is enabled on all externally registered domains.
43
41
 
44
42
  ### IP Address Management
45
43
 
46
- 15. IP allocations are managed through the approved IPAM vendor (documented in the internal vendor registry).
47
- 16. All IP blocks are tagged with owner, environment, and region.
48
- 17. IPv4 and IPv6 are both required for all new public-facing services. Dual-stack is the baseline; IPv4-only
49
- services are not accepted for new deployments.
50
- 18. Private RFC 1918 ranges are used for internal pod and node networks. Overlap with VPN or on-premises
51
- ranges must be approved through the IPAM change process.
44
+ 1. IP allocations are managed through the approved IPAM vendor (documented in the internal vendor registry).
45
+ 2. All IP blocks are tagged with owner, environment, and region.
46
+ 3. IPv4 and IPv6 are both required for all new public-facing services. Dual-stack is the baseline; IPv4-only services
47
+ are not accepted for new deployments.
48
+ 4. Private RFC 1918 ranges are used for internal pod and node networks. Overlap with VPN or on-premises ranges must be
49
+ approved through the IPAM change process.
52
50
 
53
51
  ## DNS
54
52
 
55
53
  ### Zone Management
56
54
 
57
- 19. DNS zones are managed through the approved DNS vendor (documented in the internal vendor registry).
58
- Manual zone edits via vendor UI are prohibited except in a documented break-glass scenario.
59
- 20. All zone changes are driven from infrastructure-as-code (Terraform, Pulumi, or equivalent) in a
60
- version-controlled repository. Zone state is reconciled on every deployment.
61
- 21. Internal and external zones are segregated. Internal zone resolvers are not exposed to the public internet.
55
+ 1. DNS zones are managed through the approved DNS vendor (documented in the internal vendor registry). Manual zone edits
56
+ via vendor UI are prohibited except in a documented break-glass scenario.
57
+ 2. All zone changes are driven from infrastructure-as-code (Terraform, Pulumi, or equivalent) in a version-controlled
58
+ repository. Zone state is reconciled on every deployment.
59
+ 3. Internal and external zones are segregated. Internal zone resolvers are not exposed to the public internet.
62
60
 
63
61
  ### Required Record Types
64
62
 
65
- 22. Public services must expose: A, AAAA, and the appropriate CNAME or alias records.
66
- 23. Email domains must have SPF, DKIM, and DMARC records. DMARC policy must be `p=quarantine` or stricter
67
- in production.
68
- 24. Wildcard DNS records (`*.example.com`) are permitted only for edge/ingress controllers that validate
69
- per-host routing internally. Wildcard records must not resolve to application pods directly.
70
- 25. Internal service discovery uses a dedicated internal TLD (e.g., `.internal` or cluster DNS suffix).
71
- Internal records are never propagated to public resolvers.
63
+ 1. Public services must expose: A, AAAA, and the appropriate CNAME or alias records.
64
+ 2. Email domains must have SPF, DKIM, and DMARC records. DMARC policy must be `p=quarantine` or stricter in production.
65
+ 3. Wildcard DNS records (`*.example.com`) are permitted only for edge/ingress controllers that validate per-host routing
66
+ internally. Wildcard records must not resolve to application pods directly.
67
+ 4. Internal service discovery uses a dedicated internal TLD (e.g., `.internal` or cluster DNS suffix). Internal records
68
+ are never propagated to public resolvers.
72
69
 
73
70
  ## TLS and Certificate Management
74
71
 
75
72
  ### Certificate Issuance
76
73
 
77
- 26. TLS certificates for public endpoints are issued from an approved CA vendor (documented in the internal
78
- vendor registry). Self-signed certificates are prohibited on any public-facing endpoint.
79
- 27. Certificate issuance and renewal must use the ACME protocol. Manual certificate downloads and uploads
80
- are prohibited except in a documented break-glass scenario.
81
- 28. Certificates are provisioned and renewed automatically. Expiry-based alerts fire at 30 days and again
82
- at 7 days before expiration.
74
+ 1. TLS certificates for public endpoints are issued from an approved CA vendor (documented in the internal vendor
75
+ registry). Self-signed certificates are prohibited on any public-facing endpoint.
76
+ 2. Certificate issuance and renewal must use the ACME protocol. Manual certificate downloads and uploads are prohibited
77
+ except in a documented break-glass scenario.
78
+ 3. Certificates are provisioned and renewed automatically. Expiry-based alerts fire at 30 days and again at 7 days
79
+ before expiration.
83
80
 
84
81
  ### Mutual TLS (mTLS)
85
82
 
86
- 29. East-west traffic between services must use mTLS. Plaintext service-to-service communication is
87
- prohibited in staging and production.
88
- 30. mTLS certificates are issued by the internal PKI (SPIFFE/SPIRE or equivalent). Leaf certificates
89
- rotate on a maximum 24-hour lifetime. Rotation is automatic and zero-downtime.
90
- 31. mTLS policy is enforced at the service mesh layer, not individually per application.
83
+ 1. East-west traffic between services must use mTLS. Plaintext service-to-service communication is prohibited in staging
84
+ and production.
85
+ 2. mTLS certificates are issued by the internal PKI (SPIFFE/SPIRE or equivalent). Leaf certificates rotate on a maximum
86
+ 24-hour lifetime. Rotation is automatic and zero-downtime.
87
+ 3. mTLS policy is enforced at the service mesh layer, not individually per application.
91
88
 
92
89
  ### Supported Protocols and Cipher Suites
93
90
 
94
- 32. Minimum TLS version: TLS 1.2. TLS 1.3 is required for all new integrations.
95
- 33. TLS 1.0 and TLS 1.1 are prohibited on all endpoints.
96
- 34. Cipher suites: prefer ECDHE key exchange with AES-GCM or ChaCha20-Poly1305. RC4, DES, 3DES,
97
- and export-grade ciphers are prohibited. DHE suites below 2048-bit DH parameters are prohibited.
98
- 35. HSTS is required on all HTTPS endpoints with `max-age` of at least one year.
91
+ 1. Minimum TLS version: TLS 1.2. TLS 1.3 is required for all new integrations.
92
+ 2. TLS 1.0 and TLS 1.1 are prohibited on all endpoints.
93
+ 3. Cipher suites: prefer ECDHE key exchange with AES-GCM or ChaCha20-Poly1305. RC4, DES, 3DES, and export-grade ciphers
94
+ are prohibited. DHE suites below 2048-bit DH parameters are prohibited.
95
+ 4. HSTS is required on all HTTPS endpoints with `max-age` of at least one year.
99
96
 
100
97
  ## Web Application Firewall (WAF) and DDoS
101
98
 
102
99
  ### WAF
103
100
 
104
- 36. All public HTTP/HTTPS ingress passes through the WAF layer before reaching application pods.
105
- 37. The OWASP Core Rule Set (CRS) is enforced in blocking mode. Detection-only mode is not
106
- acceptable for production. Tuning exceptions require a documented rule with justification.
107
- 38. WAF rules are version-controlled. Rule changes follow the RFC process.
108
- 39. WAF logs are ingested into the central log aggregation platform with a minimum 90-day retention.
101
+ 1. All public HTTP/HTTPS ingress passes through the WAF layer before reaching application pods.
102
+ 2. The OWASP Core Rule Set (CRS) is enforced in blocking mode. Detection-only mode is not acceptable for production.
103
+ Tuning exceptions require a documented rule with justification.
104
+ 3. WAF rules are version-controlled. Rule changes follow the RFC process.
105
+ 4. WAF logs are ingested into the central log aggregation platform with a minimum 90-day retention.
109
106
 
110
107
  ### DDoS Protection
111
108
 
112
- 40. All public endpoints are protected by volumetric DDoS mitigation at the network edge.
113
- 41. L7 DDoS (HTTP flood) mitigation is enforced by the WAF layer with rate limiting rules.
114
- 42. Incident response runbooks for DDoS events are maintained and reviewed quarterly.
109
+ 1. All public endpoints are protected by volumetric DDoS mitigation at the network edge.
110
+ 2. L7 DDoS (HTTP flood) mitigation is enforced by the WAF layer with rate limiting rules.
111
+ 3. Incident response runbooks for DDoS events are maintained and reviewed quarterly.
115
112
 
116
113
  ## Security Headers
117
114
 
118
- 43. All HTTP responses from public endpoints must include:
119
- - `Strict-Transport-Security: max-age=31536000; includeSubDomains; preload`
120
- - `X-Content-Type-Options: nosniff`
121
- - `X-Frame-Options: DENY`
122
- - `Content-Security-Policy` (service-specific, reviewed by security team)
123
- - `Referrer-Policy: strict-origin-when-cross-origin`
124
- - `Permissions-Policy` (restrictive default; expand per-service with justification)
125
- 44. `Server` and `X-Powered-By` response headers must be suppressed or overridden to not reveal
126
- implementation details.
115
+ 1. All HTTP responses from public endpoints must include:
116
+ - `Strict-Transport-Security: max-age=31536000; includeSubDomains; preload`
117
+ - `X-Content-Type-Options: nosniff`
118
+ - `X-Frame-Options: DENY`
119
+ - `Content-Security-Policy` (service-specific, reviewed by security team)
120
+ - `Referrer-Policy: strict-origin-when-cross-origin`
121
+ - `Permissions-Policy` (restrictive default; expand per-service with justification)
122
+ 2. `Server` and `X-Powered-By` response headers must be suppressed or overridden to not reveal implementation details.
127
123
 
128
124
  ## Traffic Management
129
125
 
130
126
  ### Payload Limits
131
127
 
132
- 45. Default maximum upload size: 10 MB. Services requiring larger uploads must document and configure
133
- an explicit limit; unbounded uploads are prohibited.
134
- 46. Maximum request body size: 1 MB by default for API endpoints. Streaming endpoints must implement
135
- backpressure.
136
- 47. Maximum response size is bounded by the service contract. Unbounded response streaming must
137
- implement flow control.
128
+ 1. Default maximum upload size: 10 MB. Services requiring larger uploads must document and configure an explicit limit;
129
+ unbounded uploads are prohibited.
130
+ 2. Maximum request body size: 1 MB by default for API endpoints. Streaming endpoints must implement backpressure.
131
+ 3. Maximum response size is bounded by the service contract. Unbounded response streaming must implement flow control.
138
132
 
139
133
  ### Rate Limiting
140
134
 
141
- 48. Global rate limiting is enforced at the edge/ingress layer.
142
- 49. Per-service rate limits are configured and documented in each service's runbook.
143
- 50. Rate limit responses use HTTP 429 with a `Retry-After` header.
144
- 51. Rate limit state is shared across edge replicas (e.g., Redis-backed or edge-native distributed
145
- counters). Per-replica rate limiting that allows bypass via replica selection is prohibited.
135
+ 1. Global rate limiting is enforced at the edge/ingress layer.
136
+ 2. Per-service rate limits are configured and documented in each service's runbook.
137
+ 3. Rate limit responses use HTTP 429 with a `Retry-After` header.
138
+ 4. Rate limit state is shared across edge replicas (e.g., Redis-backed or edge-native distributed counters). Per-replica
139
+ rate limiting that allows bypass via replica selection is prohibited.
146
140
 
147
141
  ### Session Management
148
142
 
149
- 52. Session tokens are cryptographically random with a minimum of 128 bits of entropy.
150
- 53. Session lifetime: configurable per service, with a maximum idle timeout of 15 minutes and a
151
- maximum absolute session lifetime of 8 hours for human-facing sessions.
152
- 54. Sessions are invalidated server-side on logout. Client-side deletion alone is insufficient.
153
- 55. Session refresh tokens must be rotated on every use.
143
+ 1. Session tokens are cryptographically random with a minimum of 128 bits of entropy.
144
+ 2. Session lifetime: configurable per service, with a maximum idle timeout of 15 minutes and a maximum absolute session
145
+ lifetime of 8 hours for human-facing sessions.
146
+ 3. Sessions are invalidated server-side on logout. Client-side deletion alone is insufficient.
147
+ 4. Session refresh tokens must be rotated on every use.
154
148
 
155
149
  ## Do Not
156
150
 
@@ -1,76 +1,131 @@
1
1
  # Release Policy
2
2
 
3
- **Maturity:** Required
4
- **Version:** 1.0.0
5
- **Last Reviewed:** 2026-03-11
3
+ **Maturity:** Required **Version:** 1.1.0 **Last Reviewed:** 2026-10-30
6
4
 
7
- This document covers how Keel versions are tagged and released. The authoritative
8
- policy lives in the [keelcore/standards `ci.md`](ci.md)
9
- under **Release Tagging**. This document is the developer-facing how-to that
10
- complements that policy.
5
+ This document covers how a project that follows the keelcore standards tags and releases a version. The authoritative
6
+ policy lives in [`ci.md`](ci.md) under **Release Tagging**. This document is the developer-facing how-to that
7
+ complements that policy. It applies to every project as written; what a project may change, and where, is set out in
8
+ [Project Customization](#project-customization) below.
11
9
 
12
10
  ---
13
11
 
14
12
  ## Versioning Specification
15
13
 
16
- Keel follows **Semantic Versioning 2.0.0** — [https://semver.org](https://semver.org).
17
- That document is the canonical reference; the rules below are derived from it and
18
- must not contradict it.
14
+ Projects follow **Semantic Versioning 2.0.0** — [https://semver.org](https://semver.org). That document is the
15
+ canonical reference; the rules below are derived from it and must not contradict it.
19
16
 
20
17
  Key semver.org rules in force here:
21
18
 
22
- - Version numbers take the form `MAJOR.MINOR.PATCH` where each component is a
23
- non-negative integer with no leading zeroes.
19
+ - Version numbers take the form `MAJOR.MINOR.PATCH` where each component is a non-negative integer with no leading
20
+ zeroes.
24
21
  - MAJOR is incremented for incompatible API changes; MINOR and PATCH reset to 0.
25
22
  - MINOR is incremented for new backward-compatible functionality; PATCH resets to 0.
26
23
  - PATCH is incremented for backward-compatible bug fixes only.
27
- - Major version zero (`0.y.z`) is for initial development. The public API is not
28
- yet stable and anything may change at any time.
24
+ - Major version zero (`0.y.z`) is for initial development. The public API is not yet stable and anything may change at
25
+ any time.
29
26
  - `1.0.0` defines the first stable public API.
30
- - Pre-release identifiers (`-alpha.1`, `-rc.2`, etc.) and build metadata (`+sha`)
31
- are valid semver extensions but are **not supported by `create-release.sh`** at
32
- this time. `--force` only accepts bare `MAJOR.MINOR.PATCH` versions.
27
+ - Pre-release identifiers (`-alpha.1`, `-rc.2`, etc.) and build metadata (`+sha`) are valid semver extensions but are
28
+ **not supported by `create-release.sh`** at this time. `--force` only accepts bare `MAJOR.MINOR.PATCH` versions.
33
29
 
34
- **Scope of automated detection:** The script detects breaking changes by diffing
35
- the config YAML field surface (`pkg/config/schema.yaml`). Other API-breaking
36
- changes — Go type signatures, HTTP API shape, security policy, behavior semantics
37
- — are not automatically detected. Use `--force` with the appropriate bump level
38
- for those cases.
30
+ Git tags are the only source of truth for versions. Projects keep no version file and make no version-bump commit.
31
+
32
+ **Scope of automated detection:** each project's hook detects only the surface it is written to diff. Breaking changes
33
+ it cannot see — Go type signatures, HTTP API shape, security policy, behavior semantics — are the developer's call: use
34
+ `--force` with the appropriate bump level.
39
35
 
40
36
  ---
41
37
 
42
38
  ## Core Rule: Tags Are Developer-Created, Never CI-Created
43
39
 
44
- CI reacts to pushed tags to run the release pipeline. CI never creates tags.
45
- A version tag is a human commitment about what a commit represents — that
46
- decision requires review and cannot be automated.
40
+ CI reacts to pushed tags to run the release pipeline. CI never creates tags. A version tag is a human commitment about
41
+ what a commit represents — that decision requires review and cannot be automated.
47
42
 
48
43
  ---
49
44
 
50
45
  ## Tooling
51
46
 
52
- | Script | Purpose |
53
- | --- | --- |
54
- | `scripts/release/create-release.sh` | Compute, approve, tag, and push a release |
55
- | `scripts/release/gen-schema.sh` | Regenerate `pkg/config/schema.yaml` after config changes |
56
- | `cmd/config-schema/main.go` | Go tool: reflection-walk of `config.Config` → dotted YAML paths |
57
- | `make gen-schema` | Alias for `gen-schema.sh` |
58
- | `make create-release` | Alias for `create-release.sh` (auto mode) |
59
- | `make create-release FORCE=v1.0.0` | Alias for `create-release.sh --force v1.0.0` |
47
+ | Script or target | Purpose |
48
+ | ------------------------------------------- | --------------------------------------------------------------------- |
49
+ | `scripts/release/create-release.sh` | Canonical (from `keelcore/standards`): compute, approve, tag and push |
50
+ | `scripts/release/create-release.local.sh` | The project's own bump hook; never canonical, never synced |
51
+ | `make create-release` | Alias for `create-release.sh` (auto mode) |
52
+ | `make create-release FORCE=v1.0.0` | Alias for `create-release.sh --force v1.0.0` |
53
+ | `make create-release DRY_RUN=1` | Alias for `create-release.sh --dry-run`: no prompt, no tag, no push |
54
+ | `make create-release-local PREV_TAG=vX.Y.Z` | Run the hook alone against a previous tag |
60
55
 
61
56
  ---
62
57
 
63
58
  ## How Version Bumps Are Computed
64
59
 
65
- The script diffs `pkg/config/schema.yaml` at the previous tag against the file
66
- at `HEAD`. The schema lists every fully-flattened dotted YAML field path in
67
- `config.Config` (e.g. `sidecar.circuit_breaker.reset_timeout`).
60
+ `create-release.sh` gets the bump level from the hook at `scripts/release/create-release.local.sh` in the git toplevel
61
+ of the repo being released — inside a consumer, the consumer's hook, never the copy in `.standards`. The driver calls it
62
+ as `create-release.local.sh <previous-tag>`, and the hook prints two lines to stdout:
63
+
64
+ 1. the bump level: `major`, `minor` or `patch`;
65
+ 2. a one-line tag subject (empty means `release vX.Y.Z`).
66
+
67
+ Anything the hook writes to stderr is shown to the developer and recorded in the tag body. A non-zero exit aborts the
68
+ release. With no hook, every release is a `patch`.
69
+
70
+ | Hook reports | Meaning | semver.org rationale |
71
+ | ------------ | ------------------------------------- | -------------------------------------------------------------------------- |
72
+ | `major` | Breaking change to the public surface | Incompatible API change ([item 4](https://semver.org/#spec-item-4)) |
73
+ | `minor` | New backward-compatible functionality | Backward-compatible addition ([item 5](https://semver.org/#spec-item-5)) |
74
+ | `patch` | No public surface change | Bug fixes and internal changes ([item 6](https://semver.org/#spec-item-6)) |
68
75
 
69
- | Schema change | Bump level | semver.org rationale |
70
- | --- | --- | --- |
71
- | One or more fields **removed** | **major** — breaking change | Removing a field is an incompatible API change ([item 4](https://semver.org/#spec-item-4)) |
72
- | One or more fields **added**, none removed | **minor** — new feature | New fields are backward-compatible additions ([item 5](https://semver.org/#spec-item-5)) |
73
- | No field surface changes | **patch** — internal improvements | No public API surface change ([item 6](https://semver.org/#spec-item-6)) |
76
+ While the major version is 0 ([item 9](https://semver.org/#spec-item-9)), `create-release.sh` demotes the bump one step
77
+ (major → minor, minor → patch) and adds `(pre-1.0 demotion)` to the tag subject. A patch stays a patch when there are
78
+ commits since the previous tag; with none, there is nothing to release.
79
+
80
+ ---
81
+
82
+ ## Project Customization
83
+
84
+ A project customizes releases in three places, all owned by the project. Everything else in this policy applies
85
+ unchanged.
86
+
87
+ | What | Where | Purpose |
88
+ | -------------------- | ------------------------------------------------------ | ------------------------------------------------ |
89
+ | Bump policy | `scripts/release/create-release.local.sh` | Decide the bump level from the project's surface |
90
+ | Hook target | `create-release-local` in the project's `Makefile` | Run the hook alone; satisfies `make audit` |
91
+ | Release how-to | The project's `docs/release-policy.md` | Document the project's customization |
92
+
93
+ ### The bump hook
94
+
95
+ - It defines the project's public surface for versioning: a config schema, an API spec, exported symbols, a changelog —
96
+ whatever a consumer of the project depends on.
97
+ - It follows the contract in [How Version Bumps Are Computed](#how-version-bumps-are-computed): called with the previous
98
+ tag, two lines on stdout, findings on stderr, non-zero exit to abort.
99
+ - It reports the raw change. It does not apply pre-1.0 demotion or add the demotion note; the driver does both.
100
+ - It is read-only and deterministic: it runs from the git toplevel against the clean `HEAD`, and never tags, commits,
101
+ pushes or writes outside temporary files.
102
+ - It is never canonical. `verify-canonical` and `governance-refresh` skip `*.local.sh`, so `keelcore/standards`' own
103
+ hook is never copied into a project and a project's hook is never overwritten.
104
+
105
+ ### The project's release-policy document
106
+
107
+ - It incorporates this policy by reference: it links to `.standards/governance/release-policy.md` and states that the
108
+ standards policy applies in full.
109
+ - It does not restate or contradict this policy. It contains only the project's customization:
110
+ - what the hook diffs, and why that is the project's public surface;
111
+ - the breaking changes the hook cannot see, which call for `--force`;
112
+ - how the hook's input is kept current (e.g. a generated schema and the check that keeps it fresh);
113
+ - project-specific tooling and anything the hook adds to the tag message.
114
+
115
+ ### Not customizable
116
+
117
+ - Tags are created only by a developer running `make create-release`; never by CI.
118
+ - `scripts/release/create-release.sh` is canonical and byte-identical in every project. Changes go to
119
+ `keelcore/standards` first.
120
+ - The preconditions, pre-1.0 demotion, `--force` rules and the signed annotated tag.
121
+ - Tags are the only version source; no version file, no version-bump commit.
122
+
123
+ ### Examples
124
+
125
+ - **keel** — the hook diffs the config field set of `pkg/config/schema.yaml` at the previous tag against `HEAD`:
126
+ removed fields → major; added fields → minor; no change → patch. See keel's `docs/release-policy.md`.
127
+ - **keelcore/standards** — the hook reads `CHANGELOG.md` `## [Unreleased]`: any `### Removed` entry → major; else any
128
+ `### Added` or `### Changed` entry → minor; else patch. An empty `[Unreleased]` fails.
74
129
 
75
130
  ---
76
131
 
@@ -79,93 +134,68 @@ at `HEAD`. The schema lists every fully-flattened dotted YAML field path in
79
134
  ```bash
80
135
  git checkout main && git pull --ff-only
81
136
  # ... merge your work ...
82
- ./scripts/release/create-release.sh
137
+ make create-release DRY_RUN=1 # optional preview
138
+ make create-release
83
139
  ```
84
140
 
85
141
  The script will:
86
142
 
87
143
  1. Verify you are on `main`, your branch's configured upstream is in sync, and the working tree is clean.
88
- 2. Compute the field diff and determine the bump level.
89
- 3. Print the summary (fields added/removed, bump level, proposed tag message).
144
+ 2. Run the project's hook and compute the next version.
145
+ 3. Print the summary (the hook's findings, bump level, proposed tag subject).
90
146
  4. Prompt for `y/N` approval.
91
- 5. On approval: create a signed annotated tag and push it to the upstream remote.
147
+ 5. On approval: create a signed annotated tag and push that one tag to the upstream remote.
92
148
 
93
- CI picks up the pushed tag and runs the release pipeline (build, sign, SBOM,
94
- publish).
149
+ A dry run prints the version and the full tag message without prompting, tagging or pushing, and reports failed
150
+ preconditions as warnings. CI picks up the pushed tag and runs the release pipeline (build, sign, SBOM, publish).
95
151
 
96
152
  ---
97
153
 
98
154
  ## Forcing a Specific Version
99
155
 
100
- Use `--force vX.Y.Z` to override the computed version. Only bare `MAJOR.MINOR.PATCH`
101
- versions are accepted — pre-release extensions (`-alpha.1`) and build metadata
102
- (`+sha`) are not supported.
103
-
104
- ```bash
105
- ./scripts/release/create-release.sh --force v1.0.0
106
- ```
156
+ Use `--force vX.Y.Z` (`make create-release FORCE=vX.Y.Z`) to override the computed version. Only bare
157
+ `MAJOR.MINOR.PATCH` versions are accepted — pre-release extensions (`-alpha.1`) and build metadata (`+sha`) are not
158
+ supported.
107
159
 
108
160
  ### Pre-1.0 rules (current major = 0)
109
161
 
110
- Per [semver.org item 9](https://semver.org/#spec-item-9), `0.y.z` is for initial
111
- development where the API is not yet stable. Only the following targets are accepted:
162
+ Per [semver.org item 9](https://semver.org/#spec-item-9), `0.y.z` is for initial development where the API is not yet
163
+ stable. Only the following targets are accepted:
112
164
 
113
165
  - Any `0.x.y` where `0.x.y > current` — normal initial-development progression.
114
- - Exactly `v1.0.0` — the stable API promotion per [semver.org item 10](https://semver.org/#spec-item-10),
115
- regardless of detected bump level.
166
+ - Exactly `v1.0.0` — the stable API promotion per [semver.org item 10](https://semver.org/#spec-item-10), regardless of
167
+ the hook's bump level.
116
168
 
117
169
  Forcing `v1.1.0`, `v2.0.0`, or any other version outside this range is rejected.
118
170
 
119
171
  ### Post-1.0 rules (current major ≥ 1)
120
172
 
121
- Once `1.0.0` is released the public API is stable. `--force` is restricted to
122
- exact single-step increments per semver.org's ordering rules:
123
-
124
- | `--force` target | Condition | Result |
125
- | --- | --- | --- |
126
- | `cur_maj.cur_min.(cur_pat+1)` | any | **Allowed** — single patch step |
127
- | `cur_maj.(cur_min+1).0` | any | **Allowed** — single minor step (e.g. internal improvements not reflected in schema diff) |
128
- | `(cur_maj+1).0.0` | breaking changes detected AND matches computed version | **Allowed** |
129
- | `(cur_maj+1).0.0` | no breaking changes, or doesn't match computed | **Rejected** — major bump requires a detected breaking change |
130
- | anything else | — | **Rejected** — must be a single-step increment |
131
-
132
- ---
133
-
134
- ## Keeping schema.yaml Fresh
135
-
136
- `pkg/config/schema.yaml` is a committed artifact generated from `config.Config`
137
- by reflection. The pre-commit hook regenerates and stages it automatically
138
- whenever any `pkg/config/*.go` file is part of a commit — no manual step required.
139
-
140
- To regenerate it outside of a commit (e.g. to inspect the current output):
141
-
142
- ```bash
143
- ./scripts/release/gen-schema.sh
144
- # or
145
- make gen-schema
146
- ```
173
+ Once `1.0.0` is released the public API is stable. `--force` is restricted to exact single-step increments per
174
+ semver.org's ordering rules:
147
175
 
148
- The consistency test suite enforces that `schema.yaml` is never stale — a
149
- mismatch between the committed file and the reflection output fails CI.
176
+ | `--force` target | Condition | Result |
177
+ | ----------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------- |
178
+ | `cur_maj.cur_min.(cur_pat+1)` | any | **Allowed** — single patch step |
179
+ | `cur_maj.(cur_min+1).0` | any | **Allowed** — single minor step (e.g. changes the hook cannot see) |
180
+ | `(cur_maj+1).0.0` | hook reports `major` AND matches computed version | **Allowed** |
181
+ | `(cur_maj+1).0.0` | hook does not report `major`, or no match | **Rejected** — a major bump requires the hook to report a breaking change |
182
+ | anything else | — | **Rejected** — must be a single-step increment |
150
183
 
151
184
  ---
152
185
 
153
186
  ## What Goes in the Tag Message
154
187
 
155
- The script auto-generates a signed annotated tag with a structured message:
188
+ The script generates a signed annotated tag with a structured message:
156
189
 
157
- - **Subject line:** `{bump}: {short summary}` (e.g. `minor: added 2 config field(s)`)
158
- - **Body:** full field list, bump level, previous/new version, commit SHA, and
159
- provenance trailers (`Generated-by:`, `Schema:`)
190
+ - **Subject line:** `{bump}: {hook subject}` (e.g. `minor: added 2 config field(s)`)
191
+ - **Body:** bump level, previous/new version, commit SHA, the hook's findings, and the `Generated-by:` trailer
160
192
 
161
- The GitHub Release body is populated directly from the tag message via
162
- `gh release create --notes-from-tag`.
193
+ The GitHub Release body can be populated directly from the tag message via `gh release create --notes-from-tag`.
163
194
 
164
195
  ---
165
196
 
166
197
  ## Emergency / Break-Glass
167
198
 
168
- If a critical patch must ship and the normal flow is blocked, follow the
169
- break-glass procedure in `docs/break-glass.md`. The bypass still requires two
170
- senior engineers and an incident record — it does not bypass the tag-creation
171
- requirement, only the precondition checks.
199
+ If a critical patch must ship and the normal flow is blocked, follow the break-glass procedure in
200
+ [`docs/break-glass.md`](../docs/break-glass.md). The bypass still requires two senior engineers and an incident record
201
+ — it does not bypass the tag-creation requirement, only the precondition checks.