@keelcore/standards 0.9.7

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/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # keelcore/standards
2
+
3
+ Engineering standards for keelcore projects. Single source of truth for coding, CI, and bash.
4
+ Consumed by AI coding tools (Claude, Cursor, Copilot) and human contributors alike.
5
+
6
+ ## Governance
7
+
8
+ | File | Scope |
9
+ | --- | --- |
10
+ | [governance/coding.md](governance/coding.md) | All code edits — scope control, reviewability, safety |
11
+ | [governance/ci.md](governance/ci.md) | CI workflows and scripts — supply chain, coverage, permissions |
12
+ | [governance/bash.md](governance/bash.md) | Bash scripts — portability, structure, logging |
13
+ | [governance/platform.md](governance/platform.md) | Platform architecture — governance process, DNS, TLS, WAF, networking |
14
+ | [governance/observability.md](governance/observability.md) | Metrics, logging, tracing, alerting |
15
+ | [governance/security.md](governance/security.md) | IAM, encryption, key management, data classification |
16
+ | [governance/runtime.md](governance/runtime.md) | Containers, orchestration, deployment, compliance |
17
+ | [governance/api-management.md](governance/api-management.md) | API design, versioning, rate limiting, quotas, gateway |
18
+
19
+ ## Consumption
20
+
21
+ ### Submodule (default, language-agnostic)
22
+
23
+ ```bash
24
+ git submodule add git@github.com:keelcore/standards .standards
25
+ git submodule update --init --recursive
26
+ ```
27
+
28
+ Reference governance from your tool adapters via relative paths (`.standards/governance/coding.md`).
29
+
30
+ Update to latest:
31
+
32
+ ```bash
33
+ git submodule update --remote .standards
34
+ git add .standards && git commit -m "chore: update standards"
35
+ ```
36
+
37
+ ### Go
38
+
39
+ ```bash
40
+ go get github.com/keelcore/standards/go
41
+ ```
42
+
43
+ Add to your repo:
44
+
45
+ ```go
46
+ //go:generate go run github.com/keelcore/standards/go/cmd/materialize .standards
47
+ ```
48
+
49
+ Then run `go generate ./...` to extract governance files to `.standards/` (gitignore this directory).
50
+
51
+ ### Node
52
+
53
+ ```bash
54
+ npm install @keelcore/standards
55
+ ```
56
+
57
+ Governance files are available under `node_modules/@keelcore/standards/governance/`.
58
+
59
+ ### Python
60
+
61
+ ```bash
62
+ pip install keelcore-standards
63
+ ```
64
+
65
+ ## Tool Adapters
66
+
67
+ Copy the relevant adapter to your repo and adjust paths if needed.
68
+
69
+ | Tool | Adapter |
70
+ | --- | --- |
71
+ | Claude Code | [adapters/claude/CLAUDE.md](adapters/claude/CLAUDE.md) |
72
+ | Cursor | [adapters/cursor/](adapters/cursor/) |
73
+ | GitHub Copilot | [adapters/copilot/copilot-instructions.md](adapters/copilot/copilot-instructions.md) |
74
+
75
+ ## Architecture Decision Records
76
+
77
+ ADRs documenting standards design decisions live in [docs/adr/](docs/adr/).
78
+
79
+ New ADRs use the template at [docs/adr/0000-template.md](docs/adr/0000-template.md).
80
+
81
+ | ADR | Decision |
82
+ | --- | --- |
83
+ | [0001](docs/adr/0001-standards-as-submodule-and-package.md) | Standards distributed as both submodule and language package |
84
+ | [0002](docs/adr/0002-coverage-baseline-in-repo.md) | Code coverage baseline stored in repository |
85
+ | [0003](docs/adr/0003-architecture-governance-process.md) | Architecture governance via DACI, RFC, and ARB |
86
+ | [0004](docs/adr/0004-workload-identity-spiffe-spire.md) | SPIFFE/SPIRE for workload identity |
87
+ | [0005](docs/adr/0005-observability-opentelemetry.md) | OpenTelemetry as the unified observability framework |
88
+ | [0006](docs/adr/0006-opa-centralized-policy-engine.md) | OPA as the centralized authorization policy engine |
89
+ | [0007](docs/adr/0007-zero-downtime-deployment.md) | Zero-downtime deployment via blue/green and canary |
90
+ | [0008](docs/adr/0008-rapid-key-rotation.md) | Rapid key rotation with a 5-minute platform SLA |
91
+ | [0009](docs/adr/0009-gitops-change-management.md) | GitOps as the declarative change management mechanism |
92
+
93
+ ## Versioning
94
+
95
+ Semantic versioning. Tags trigger publication to all package ecosystems simultaneously.
96
+
97
+ - Breaking changes (governance removals, renamed files): major version bump.
98
+ - New governance or expanded rules: minor version bump.
99
+ - Corrections, clarifications: patch version bump.
@@ -0,0 +1,66 @@
1
+ # AI Model Conduct Standards
2
+
3
+ These rules govern the behavior of AI coding assistants (Claude, Copilot, Cursor, or equivalent)
4
+ when working in projects under this governance. They are non-negotiable and take precedence over
5
+ any model-default behavior.
6
+
7
+ **Maturity:** Required
8
+ **Version:** 1.0.0
9
+ **Last Reviewed:** 2026-03-14
10
+
11
+ ## Commit Attribution
12
+
13
+ - Do NOT add `Co-Authored-By`, `Generated-by`, or any AI attribution trailer to commit messages.
14
+ - Commit messages contain only the subject, optional body, and the required DCO `Signed-off-by`
15
+ trailer. No AI branding.
16
+
17
+ ## Test File Protection
18
+
19
+ - Do NOT modify test files without explicit prior authorization from the developer.
20
+ - When a test fails, find and fix the root cause in production code. Do not adjust the test to
21
+ make it pass.
22
+ - Exceptions (adding a new test, fixing a test bug, tracking an intentional contract change)
23
+ require explicit approval in the conversation before any edit is made.
24
+
25
+ ## Test Suite Execution
26
+
27
+ - Run `make test` (or `make unit-test` at minimum) before declaring any code change complete.
28
+ - Do not hand off code without verifying the test suite passes.
29
+ - If tests cannot be run (environment constraint), state this explicitly and enumerate what was
30
+ not validated.
31
+
32
+ ## Entry Point Files Are Pinned
33
+
34
+ - `main.go` (and any program entry point file) is pinned — do not edit it without explicit
35
+ developer consent in the current conversation.
36
+ - Entry points are considered high-blast-radius: a silent change to `main` can alter startup
37
+ behavior, flag defaults, or initialization order in ways that are hard to review.
38
+
39
+ ## Bash Script Delivery
40
+
41
+ - When writing or editing a bash script, always deliver the complete script.
42
+ - Never deliver a diff, patch, or partial snippet of a bash script.
43
+ - Rationale: partial bash edits are error-prone; the complete script is reviewable as a unit.
44
+
45
+ ## Governance Supremacy
46
+
47
+ - Project governance documents (this repo's `governance/` tree) override any model default or
48
+ prior training behavior.
49
+ - When a governance rule conflicts with a model default (e.g., adding attribution, adding
50
+ speculative features, cleaning up untouched code), governance wins without exception.
51
+ - If a governance rule is ambiguous, ask for clarification rather than applying a default.
52
+
53
+ ## Scope Discipline
54
+
55
+ - Do not perform unrequested cleanup, refactoring, or improvements in files adjacent to the
56
+ current change.
57
+ - Do not add docstrings, comments, or type annotations to code not directly modified.
58
+ - Do not add error handling, fallbacks, or validation for scenarios that cannot occur.
59
+ - Surgical edits only: the smallest correct change that satisfies the request.
60
+
61
+ ## Memory and Governance Relationship
62
+
63
+ - Model memory (session-local or persisted) is subordinate to governance.
64
+ - If a memory contradicts governance, flag the conflict to the developer; do not silently pick
65
+ one over the other.
66
+ - Governance is the source of truth; memory captures working context and in-flight decisions.
@@ -0,0 +1,185 @@
1
+ # API Management Standards
2
+
3
+ These rules govern API design, versioning, rate limiting, quotas, lifecycle management, and
4
+ developer experience. They are non-negotiable unless explicitly superseded by a signed ADR.
5
+
6
+ **Maturity:** Required
7
+ **Version:** 1.0.0
8
+ **Last Reviewed:** 2026-03-09
9
+
10
+ ## API Design
11
+
12
+ ### Interface Contracts
13
+
14
+ 1. Every API exposes a machine-readable interface definition. REST APIs use OpenAPI 3.1+.
15
+ gRPC services use Protocol Buffers with buf schema registry publication. GraphQL schemas
16
+ are registered in the central schema registry.
17
+ 2. Interface definitions are version-controlled in the owning service's repository and published
18
+ to the central API catalog on every release.
19
+ 3. APIs follow a consistent resource-oriented design: nouns for resources, standard HTTP verbs
20
+ (GET, POST, PUT, PATCH, DELETE) for operations, and standard status codes.
21
+
22
+ ### Versioning
23
+
24
+ 4. All APIs are versioned. The version is embedded in the URL path for REST (`/v1/`, `/v2/`).
25
+ gRPC APIs encode the version in the package name (`keelcore.auth.v1`).
26
+ 5. A version is considered stable once it is deployed to production. Stable versions must not
27
+ receive breaking changes. Add a new version instead.
28
+ 6. Breaking changes are defined as: removing or renaming a field, changing a field's type,
29
+ removing an endpoint, changing required/optional status of a field, or changing error semantics.
30
+ 7. Non-breaking additions (new optional fields, new endpoints, new enum values that do not affect
31
+ existing behavior) may be made to a stable version.
32
+ 8. Deprecated versions remain available for a minimum of 6 months after the successor version
33
+ is available. Deprecation is communicated via the `Deprecation` and `Sunset` response headers
34
+ and in the API catalog.
35
+
36
+ ### Error Responses
37
+
38
+ 9. All error responses use a consistent envelope:
39
+
40
+ ```json
41
+ {
42
+ "error": {
43
+ "code": "RESOURCE_NOT_FOUND",
44
+ "message": "The requested resource was not found.",
45
+ "request_id": "<trace_id>",
46
+ "details": []
47
+ }
48
+ }
49
+ ```
50
+
51
+ 10. `code` is a machine-readable uppercase string constant. `message` is human-readable.
52
+ HTTP status codes and `code` values are documented in the API catalog.
53
+ 11. Error messages must not include internal implementation details (stack traces, SQL queries,
54
+ internal service names) in production responses.
55
+
56
+ ### Pagination
57
+
58
+ 12. All list endpoints that may return more than 100 items support cursor-based pagination.
59
+ Offset pagination is acceptable only for internal tooling APIs.
60
+ 13. Page size has a maximum (default 100, configurable per endpoint, maximum 1000). Requests
61
+ for unlimited results are rejected.
62
+ 14. Pagination response includes: `next_cursor`, `prev_cursor` (where supported), `total_count`
63
+ (where computationally feasible), and `has_more`.
64
+
65
+ ## Rate Limiting
66
+
67
+ ### Global Limits
68
+
69
+ 15. All API traffic passes through the centralized rate limiting layer before reaching service pods.
70
+ 16. Global rate limits protect the platform from aggregate overload. These are set by the platform
71
+ team and documented in the API catalog.
72
+ 17. Rate limit enforcement uses a token bucket algorithm. Burst capacity may be up to 2× the
73
+ sustained rate for a window of up to 5 seconds.
74
+
75
+ ### Per-Service and Per-Client Limits
76
+
77
+ 18. Every API defines explicit rate limits per endpoint. Rate limits are documented in the API
78
+ catalog alongside the endpoint specification.
79
+ 19. Rate limits are enforced per API key, per client identity (OAuth client ID), or per
80
+ authenticated user, in that priority order. Unauthenticated traffic shares a shared
81
+ anonymous pool with a lower limit.
82
+ 20. Services configure rate limits independently of the global limit; the effective limit is
83
+ the minimum of the global and per-service limits.
84
+ 21. Rate limit state is stored in a shared, distributed backend (e.g., Redis). Per-replica
85
+ in-memory rate limiting that can be bypassed by round-robin routing is prohibited.
86
+
87
+ ### Rate Limit Responses
88
+
89
+ 22. Rate-limited requests receive HTTP 429 with:
90
+ - `Retry-After` header (seconds until the client may retry)
91
+ - `X-RateLimit-Limit` (requests allowed per window)
92
+ - `X-RateLimit-Remaining` (requests remaining in the current window)
93
+ - `X-RateLimit-Reset` (Unix timestamp of window reset)
94
+ - A JSON body following the standard error envelope (rule 9 above)
95
+ 23. Rate limit headers (`X-RateLimit-*`) are included on every API response, not only on 429s.
96
+
97
+ ### Quotas
98
+
99
+ 24. Usage quotas (distinct from rate limits) enforce longer-window consumption caps (daily,
100
+ monthly) per API key or tenant. Quotas are configured in the API management platform.
101
+ 25. Quota exhaustion returns HTTP 429 with `code: QUOTA_EXCEEDED` and a `Retry-After` pointing
102
+ to the quota reset time.
103
+ 26. Quota alerts fire at 80% and 95% consumption to allow clients and operators to take action
104
+ before hard limits are hit.
105
+
106
+ ## Authentication and Authorization
107
+
108
+ ### API Key Management
109
+
110
+ 27. API keys are issued by the central API management platform. Hand-crafted keys are prohibited.
111
+ 28. API keys are associated with: an owner (human or service), a set of allowed scopes, an
112
+ expiry date, and an environment (keys do not cross environment boundaries).
113
+ 29. API keys are rotated on a maximum 12-month cycle. Keys that have not been rotated are
114
+ automatically expired and flagged for the owner.
115
+ 30. API key usage is logged per request with: key ID (never the key value), endpoint, response
116
+ code, and latency.
117
+ 31. Compromised API keys are revoked within 5 minutes of detection. The platform supports
118
+ emergency bulk revocation for a compromised credential class.
119
+
120
+ ### Scopes and Permissions
121
+
122
+ 32. API scopes are defined at the resource level and follow the pattern `{resource}:{action}`
123
+ (e.g., `orders:read`, `orders:write`). Wildcard scopes are prohibited for non-admin APIs.
124
+ 33. Clients are issued only the scopes they need. Over-provisioned scopes are flagged in the
125
+ quarterly access review.
126
+ 34. Admin scopes require MFA-protected issuance and a shorter expiry (maximum 8 hours).
127
+
128
+ ## API Gateway
129
+
130
+ ### Gateway Responsibilities
131
+
132
+ 35. The API gateway handles: routing, authentication verification, rate limiting, WAF enforcement,
133
+ request/response logging, and protocol translation (where needed).
134
+ 36. Business logic must not reside in gateway configuration. Transformation rules that implement
135
+ business semantics belong in the upstream service.
136
+ 37. Gateway configuration is version-controlled and deployed via the same GitOps pipeline as
137
+ application code. Manual console edits are prohibited in staging and production.
138
+
139
+ ### Backend Health and Circuit Breaking
140
+
141
+ 38. The gateway enforces circuit breakers for each upstream service. A service that exceeds the
142
+ error threshold (5xx rate > 50% for 10 seconds) is circuit-broken; traffic is rejected at
143
+ the gateway with HTTP 503 until the upstream recovers.
144
+ 39. Upstream health checks are configured per route. The gateway does not route to unhealthy
145
+ upstreams.
146
+
147
+ ### Observability at the Gateway
148
+
149
+ 40. The gateway emits per-endpoint metrics: request rate, error rate, latency (p50/p95/p99),
150
+ and upstream latency. These are automatically included in each API's RED dashboard.
151
+ 41. Every request is tagged with a `request_id` injected or verified by the gateway. The
152
+ `request_id` matches the `trace_id` in distributed traces.
153
+
154
+ ## API Lifecycle
155
+
156
+ ### Catalog and Discovery
157
+
158
+ 42. Every API is registered in the central API catalog before its first production deployment.
159
+ Unregistered APIs are blocked by admission control.
160
+ 43. The catalog entry includes: service owner, SLO, rate limits, authentication requirements,
161
+ versioning status, deprecation dates (if applicable), and a link to the OpenAPI/Protobuf spec.
162
+ 44. The catalog is queryable by all engineers and is the single source of truth for API
163
+ discoverability.
164
+
165
+ ### Deprecation and Retirement
166
+
167
+ 45. API retirement follows a defined process: announce deprecation (Sunset header + catalog
168
+ update), monitor usage, notify active consumers, enforce sunset date.
169
+ 46. An API version with active traffic cannot be retired without consumer migration or an
170
+ explicitly accepted exception.
171
+ 47. The `Sunset` date is set in UTC and announced at least 6 months in advance for externally
172
+ consumed APIs, 30 days for internal-only APIs.
173
+
174
+ ## Do Not
175
+
176
+ - Publish an API without an OpenAPI/Protobuf spec in the central catalog.
177
+ - Make breaking changes to a stable API version.
178
+ - Issue API keys outside the central API management platform.
179
+ - Cross environment boundaries with API keys.
180
+ - Return internal stack traces or SQL errors in production API responses.
181
+ - Implement rate limiting as per-replica in-memory state.
182
+ - Configure the gateway with business logic or inline transformations.
183
+ - Retire an API version with active consumers without an approved migration plan.
184
+ - Return list endpoints without pagination for collections that may grow beyond 100 items.
185
+ - Omit rate limit headers from API responses.
@@ -0,0 +1,110 @@
1
+ # Architecture Review Board (ARB)
2
+
3
+ **Maturity:** Required
4
+ **Version:** 1.0.0
5
+ **Last Reviewed:** 2026-03-09
6
+
7
+ The ARB is the governing body for platform architecture decisions. It is the default Approver
8
+ for all architecture ADRs and the final escalation point for cross-domain technical disputes.
9
+
10
+ ## Membership
11
+
12
+ ### Composition
13
+
14
+ 1. The ARB has a minimum of 5 members and a maximum of 9 members.
15
+ 2. Members are nominated by the CTO and serve a minimum term of 12 months.
16
+ 3. Required seats (one each): Infrastructure, Security, Observability, Data, Application Platform.
17
+ 4. The CTO or a designated VP of Engineering holds the Chair seat. The Chair is a non-voting
18
+ member except to break ties (see Tie-Breaking below).
19
+ 5. Membership is documented in the internal people directory and in this file's companion
20
+ `docs/arb-members.md` (maintained separately from this governance doc to avoid churn).
21
+
22
+ ### Eligibility
23
+
24
+ 6. Members must be active engineers or engineering managers with direct responsibility in their
25
+ domain. Advisory or external members are not permitted.
26
+ 7. A member who misses three consecutive meetings without proxy is automatically suspended
27
+ pending review by the Chair.
28
+
29
+ ## Meeting Cadence
30
+
31
+ 8. The ARB meets on a defined monthly cadence. The meeting schedule is published at the start
32
+ of each quarter.
33
+ 9. Emergency sessions may be called by the Chair or by any two members with a minimum 24-hour
34
+ notice. Emergency sessions are limited to the agenda item that triggered them.
35
+ 10. Meeting notes and decisions are published to the engineering wiki within five business days.
36
+ 11. Decisions made asynchronously (via PR approval or written vote) are recorded with the same
37
+ formality as decisions made in session.
38
+
39
+ ## Decision-Making
40
+
41
+ ### Quorum
42
+
43
+ 12. Quorum is a simple majority of seated, non-suspended members (excluding the Chair):
44
+ - 5 members: quorum = 3
45
+ - 7 members: quorum = 4
46
+ - 9 members: quorum = 5
47
+ 13. Decisions made without quorum are provisional. They must be ratified at the next session
48
+ where quorum is present, or they are void.
49
+ 14. A member who has a conflict of interest in a decision must declare it and recuse. Recused
50
+ members do not count toward quorum for that item.
51
+
52
+ ### Voting
53
+
54
+ 15. Decisions are made by simple majority of present, non-recused members.
55
+ 16. Votes are recorded (member, vote, rationale for dissent) in the meeting notes.
56
+ 17. Abstentions are permitted but discourage without stated reason.
57
+
58
+ ### Tie-Breaking
59
+
60
+ 18. When a vote is tied, the Chair casts the deciding vote.
61
+ 19. The Chair must state their rationale when casting a tie-breaking vote.
62
+ 20. Tie-breaking decisions are flagged in meeting notes for CTO awareness.
63
+
64
+ ## Scope of Authority
65
+
66
+ 21. The ARB has authority over: platform architecture standards, vendor selection for
67
+ platform-level tooling, cross-domain API contracts, security architecture, and
68
+ data architecture patterns.
69
+ 22. The ARB does not have authority over: team-internal implementation choices, product
70
+ roadmap prioritization, staffing decisions, or budget approval.
71
+ 23. Any team may bring a decision to the ARB for guidance even if ARB approval is not strictly
72
+ required. The ARB may accept or decline to review.
73
+
74
+ ## ADR and RFC Approval
75
+
76
+ 24. The ARB is the default Approver for all ADRs tagged as architecture decisions.
77
+ 25. The ARB reviews RFCs at the Under Discussion stage. Approval converts an RFC to Accepted
78
+ and triggers ADR creation.
79
+ 26. The ARB may delegate Approver authority for a specific decision to a named individual or
80
+ sub-committee. Delegation is recorded in the relevant ADR/RFC.
81
+ 27. The ARB chair may approve urgent security-related ADRs unilaterally with 24-hour notice
82
+ to the full board, subject to ratification at the next session.
83
+
84
+ ## Escalation
85
+
86
+ 28. Escalation path: Team → Domain Lead → ARB → CTO.
87
+ 29. Any engineer may escalate a decision to the ARB by opening a GitHub issue in the
88
+ `keelcore/standards` repository labeled `arb-escalation`.
89
+ 30. The ARB must acknowledge escalations within three business days and schedule review
90
+ within ten business days.
91
+ 31. ARB decisions may be escalated to the CTO only on the grounds of: process violation,
92
+ conflict of interest, or organizational policy conflict. The CTO's decision is final.
93
+ 32. Escalation outcomes are published (with appropriate confidentiality) in the engineering wiki.
94
+
95
+ ## Standards Cadence
96
+
97
+ 33. The ARB publishes a versioned Architecture Standards snapshot at least once per quarter.
98
+ The snapshot is tagged in this repository as `arb-standards-YYYY-QN`.
99
+ 34. Each quarterly snapshot includes: new ADRs accepted since the last snapshot, standards
100
+ that changed maturity level, deprecated standards, and open RFCs in progress.
101
+ 35. The ARB reviews all standards marked `Deprecated` at least once per year to confirm
102
+ retirement or rescind deprecation.
103
+
104
+ ## Do Not
105
+
106
+ - Allow a decision with quorum impact to proceed if quorum is not met.
107
+ - Allow a member to vote on a decision where they have a declared conflict of interest.
108
+ - Publish meeting notes without removing information subject to legal hold or HR confidentiality.
109
+ - Allow the Chair to cast a non-tie-breaking vote.
110
+ - Make ARB decisions without recording them in the engineering wiki within five business days.
@@ -0,0 +1,147 @@
1
+ # Bash Script Standards
2
+
3
+ These rules govern all bash scripts in this project. They are non-negotiable.
4
+
5
+ **Maturity:** Required
6
+ **Version:** 1.0.0
7
+ **Last Reviewed:** 2026-03-09
8
+
9
+ ## Portability and Shell Baseline
10
+
11
+ 1. Google Bash style unless overridden by rules below.
12
+ 2. Darwin-compatible; Bash 3.x compatible — no `mapfile`, no `declare -A`.
13
+ 3. Shebang: `#!/usr/bin/env bash` always.
14
+ 4. Use the explicit shell options block (not `set -euo pipefail` shorthand).
15
+
16
+ ## Required Shell Options Block
17
+
18
+ Every script begins with:
19
+
20
+ ```bash
21
+ #!/usr/bin/env bash
22
+ # <script name>
23
+ # <functional description, with arguments if needed>
24
+
25
+ # bash configuration:
26
+ # 1) Exit script if you try to use an uninitialized variable.
27
+ set -o nounset
28
+
29
+ # 2) Exit script if a statement returns a non-true return value.
30
+ set -o errexit
31
+
32
+ # 3) Use the error status of the first failure, rather than that of the last item in a pipeline.
33
+ set -o pipefail
34
+ ```
35
+
36
+ ## Script Structure
37
+
38
+ 5. `main` is the FIRST function defined in the file.
39
+ 6. `main "${@:-}"` is the LAST line of the file.
40
+ 7. Always provide the complete script, never a patch or diff.
41
+ 8. Never omit any referenced function.
42
+
43
+ ## Functions
44
+
45
+ 9. All function declarations preceded by keyword `function`.
46
+ 10. No `function_` prefix on function names.
47
+ 11. Function names: lowercase, simple, reflect intent.
48
+ 12. One statement per line.
49
+ 13. No function body > 10 lines unless reduction is genuinely impossible.
50
+ 14. Prefer decomposition into small, reusable helpers.
51
+ 15. Load-bearing functions: log intent first, then log result.
52
+
53
+ ## Arguments and Validation
54
+
55
+ 16. Validate argument count.
56
+ 17. Validate required arguments are non-null.
57
+ 18. Support empty argument lists under `set -u` using `"${@:-}"`.
58
+ 19. `validate_args` must allow an empty first argument when zero arguments are valid:
59
+
60
+ ```bash
61
+ function validate_args() {
62
+ if [ "${#}" -gt 0 ] && [ -z "${1:-}" ]; then
63
+ log '❌ Error: Unexpected empty argument'
64
+ exit 1
65
+ fi
66
+ }
67
+ ```
68
+
69
+ ## Variables
70
+
71
+ 20. Always `"${var}"` style for variable expansion.
72
+ 21. Quote expansions unless unquoted behavior is explicitly required.
73
+ 22. `local -r` for immutable locals NOT assigned from subshell.
74
+ 23. Mutable locals or subshell-assigned: declare on one line, assign on next:
75
+
76
+ ```bash
77
+ local result
78
+ result="$(some_command)"
79
+ ```
80
+
81
+ 24. Single quotes for literal strings with no expansion needed.
82
+
83
+ ## Logging and Output
84
+
85
+ 25. Route all messaging through a `log` function.
86
+ 26. `log` must tee to a well-known log file.
87
+ 27. Capture original stdout in `main` with `exec 5>&1`; `log` writes to FD 5.
88
+ 28. Use emoji for key accomplishments.
89
+ 29. Load-bearing functions log both intent and result.
90
+
91
+ ## Printing
92
+
93
+ 30. Prefer `printf` over `echo`.
94
+ 31. `printf` format string must be single-quoted with explicit format specifiers.
95
+ 32. Use `echo` only with no parameters and no formatting concerns.
96
+
97
+ ## Standard `log` Function Pattern
98
+
99
+ ```bash
100
+ function log() {
101
+ local msg
102
+ msg="${1:-}"
103
+ printf '%s\n' "${msg}" | tee -a '/tmp/SCRIPTNAME.log' >&5
104
+ }
105
+ ```
106
+
107
+ ## Standard `main` Pattern
108
+
109
+ ```bash
110
+ function main() {
111
+ exec 5>&1
112
+ validate_args "${@:-}"
113
+ # ... work ...
114
+ }
115
+ # ... helper functions ...
116
+ main "${@:-}"
117
+ ```
118
+
119
+ ## GitHub Actions `validate_args` Quirk
120
+
121
+ In GitHub Actions, `shell: bash` expands an empty `$@` to a single empty-string argument when
122
+ using `"${@:-}"`. Unadjusted, this triggers the argument check spuriously. Apply the following
123
+ fixups based on script type:
124
+
125
+ **Zero-argument scripts** — tolerate the single-empty-string artifact; reject any real arg:
126
+
127
+ ```bash
128
+ function validate_args() {
129
+ if [ "${#}" -gt 1 ] || [ -n "${1:-}" ]; then
130
+ log '❌ Error: Unexpected argument'
131
+ exit 1
132
+ fi
133
+ }
134
+ ```
135
+
136
+ **Argument-taking scripts** — tolerate the artifact; still catch multiple or non-empty extra args:
137
+
138
+ ```bash
139
+ function validate_args() {
140
+ if [ "${#}" -gt 1 ] && [ -n "${1:-}" ]; then
141
+ log '❌ Error: Too many arguments'
142
+ exit 1
143
+ fi
144
+ }
145
+ ```
146
+
147
+ Do NOT change the call site from `"${@:-}"` to `"$@"` — fix the body only.