CI/CD Pipeline Conventions¶
Document ID: CI-CONV-001 Version: 1.0.0 Owner: Platform Engineering Lead Last Updated: 2026-05-25 Review Cycle: Quarterly (next review: 2026-08-25) Classification: Internal — Operational
1. Purpose and Scope¶
This document is the authoritative governing standard for all CI/CD pipeline
artifacts and commit conventions in this repository. It supersedes and
incorporates .github/COMMIT_CONVENTION.md (archived 2026-05-25).
It governs: commit message format, workflow file structure, in-file changelog format, sequential CI identifier tracking, action versioning and SHA-pinning policy, digest reference block scope, SARIF seeding, gate type definitions, secret and permission hygiene, artifact retention, and concurrency policy.
All pipeline changes must conform to this document. Where any thread-level
instruction or external guide conflicts with this document, this document wins.
Deviations require an explicit [GAP: ...] declaration and resolution before
the change is merged.
Governing standards (authoritative external sources):
| Standard | Authority | Governs |
|---|---|---|
| Conventional Commits v1.0.0 | conventionalcommits.org | Commit message format |
| Semantic Versioning v2.0.0 | semver.org | Version bump rules |
| NIST SP 800-218 SSDF v1.1 | csrc.nist.gov | CI security gate design |
| NIST SP 800-161r1 C-SCRM | csrc.nist.gov | Action supply-chain pinning |
| GitHub Security Hardening for Actions | docs.github.com | Permission scope, secret hygiene |
| GitHub Build System Best Practices | docs.github.com | Artifact provenance, immutable builds |
2. Commit Message Convention¶
(Migrated from .github/COMMIT_CONVENTION.md, archived 2026-05-25)
2.1 Format¶
```
[optional body — bullet details, reasoning]
[optional footer — Closes #42, BREAKING CHANGE: ...] ```
2.2 Rules¶
- Subject line ≤ 72 characters
- Use imperative mood: "add", "fix", "remove" — not "added", "fixed"
- No capitalisation of first word after the colon
- No period at end of subject line
- Reference issues in footer:
Closes #42
2.3 Types¶
| Type | When to use |
|---|---|
feat |
New feature or capability |
fix |
Bug fix |
ci |
CI/CD pipeline changes |
chore |
Maintenance, dependency bumps, tooling |
docs |
Documentation only |
refactor |
Code restructure with no behaviour change |
test |
Adding or updating tests |
perf |
Performance improvement |
revert |
Reverting a previous commit |
2.4 Scope (optional)¶
Use the affected module or area: api, auth, db, observability, ci,
deps, docs, sast, test
2.5 Examples¶
``` feat(auth): add argon2 password hashing for user credentials
fix(ci): pin codeql upload-sarif to v3 — v4 breaks sarif path resolution
chore(deps): bump pytest-asyncio 0.24.0 -> 0.26.0
ci(sast): add bandit hard gate for medium severity findings ```
2.6 What to Avoid¶
fix stuff/WIP/asdf/misc- Vague messages like
update codeorchanges - Multiple unrelated changes in one commit — split them
3. Workflow File Inventory¶
| File | Purpose | Trigger |
|---|---|---|
.github/workflows/secured_ci.yml |
Primary security-gated CI pipeline | push: main/develop; PR: main |
.github/workflows/docs.yml |
MkDocs site build and GitHub Pages deploy | push: main; PR: main; workflow_dispatch |
.github/workflows/mermaid-render.yml |
Render docs/diagrams/*.mmd to PNG; commits output back to branch |
push: main/develop on *.mmd path; workflow_dispatch |
File naming convention: kebab-case.yml. Workflow files must declare a
meaningful name: field matching their stated purpose.
4. In-File Changelog Convention¶
Every workflow file must carry a changelog header block in the following format, immediately below the file-level descriptive comment block:
```
Changelog (Conventional Commits — type(scope): summary):¶
YYYY-MM-DD vX.Y.Z CI-NN type(scope): summary¶
```
4.1 CI Identifier (CI-NN)¶
- Every changelog entry carries a globally sequential CI identifier:
CI-01,CI-02, ...CI-NN. - The identifier is globally sequential across all workflow files —
not per-file. CI-36 in
docs.ymlfollows CI-35 insecured_ci.yml. - The identifier never resets. Reverted entries retain their original
CI-NN; the revert commit receives its own new identifier. - Current highest identifier: CI-36. Update this line in the same commit as any new changelog entry.
4.2 Version Bump Rules (semver.org v2.0.0)¶
| Change type | Bump | Example |
|---|---|---|
| Breaking: gate order change, job removal, permission scope reduction | major | v1.x.x → v2.0.0 |
| New job, new security gate, new tool integration | minor | v1.2.x → v1.3.0 |
| Bug fix, path correction, config adjustment, dep bump | patch | v1.2.3 → v1.2.4 |
| Docs-only, comment update, chore | patch | v1.2.3 → v1.2.4 |
Version is per-workflow-file — secured_ci.yml and docs.yml version
independently.
4.3 Commit Type Mapping for CI Changelog¶
| Type | When to use in CI context |
|---|---|
feat |
New job, new security scanner, new test tier |
fix |
Correcting a broken step, wrong path, failed action pin |
refactor |
Restructuring jobs without behaviour change |
chore |
Dependency bumps, SHA updates, comment cleanup |
docs |
Workflow comment or reference block update only |
ci |
Trigger event changes, concurrency policy changes |
5. Action Versioning and SHA-Pinning Policy¶
All third-party and GitHub-owned actions must be pinned to a full commit SHA, not a mutable tag. Mutable tags can be silently moved to a malicious commit without any change visible in the workflow file — SHA pinning is the only defense against this class of supply-chain attack.
Governing authority: NIST SP 800-161r1 §3.1 (C-SCRM: verify integrity of third-party components); GitHub Security Hardening for Actions — "Using third-party actions."
5.1 Pinning Format¶
```yaml
Correct — full SHA pin with tag comment:¶
uses: actions/checkout@ef36d1093e2975a02b4c6a03c8b73990a59a478f # v4.3.0
Incorrect — mutable tag:¶
uses: actions/checkout@v4.3.0
Incorrect — branch ref:¶
uses: actions/checkout@main ```
5.2 SHA Verification¶
SHAs must be verified via the GitHub API (get_tag or get_commit) before
being written into a workflow file. Do not copy SHAs from third-party sources
without independent verification. Verification command:
bash
gh api repos/<owner>/<repo>/git/ref/tags/<tag>
5.3 Per-File Digest Reference Block¶
Each workflow file must carry a digest reference block documenting the actions used in that file only. The block is scoped to the file — it does not duplicate actions from other workflow files.
This document (Section 5.4) is the authoritative cross-repository reference map.
Format:
```
=============================================================================¶
ACTION SHA DIGEST REFERENCE (actions used in this workflow):¶
@¶
-> ¶
=============================================================================¶
```
5.4 Cross-Repository Action Reference Map¶
| Action | Tag | Verified SHA | Used In |
|---|---|---|---|
actions/checkout |
v4.3.0 | ef36d1093e2975a02b4c6a03c8b73990a59a478f |
secured_ci.yml, docs.yml, mermaid-render.yml |
actions/setup-python |
v5.6.0 | a26af69be951a213d495a4c3e4e4022e16d87065 |
secured_ci.yml, docs.yml |
actions/setup-node |
v4.4.0 | 49933ea5288caeca8642d1e84afbd3f7d6820020 |
mermaid-render.yml |
actions/upload-artifact |
v4.6.2 | ea165f8d65b6e75b540449e92b4886f43607fa02 |
secured_ci.yml |
docker/setup-buildx-action |
v4.1.0 | d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 |
secured_ci.yml |
docker/build-push-action |
v7.2.0 | f9f3042f7e2789586610d6e8b85c8f03e5195baf |
secured_ci.yml |
aquasecurity/trivy-action |
v0.36.0 (apt path) | ed142fd0673e97e23eac54620cfb913e5ce36c25 |
secured_ci.yml |
anchore/sbom-action |
v0.24.0 | e22c389904149dbc22b58101806040fa8d37a610 |
secured_ci.yml |
trufflesecurity/trufflehog |
v3.95.3 | 37b77001d0174ebec2fcca2bd83ff83a6d45a3ab |
secured_ci.yml |
github/codeql-action/* |
v4.36.0 | 7211b7c8077ea37d8641b6271f6a365a22a5fbfa |
secured_ci.yml |
actions/dependency-review-action |
v5.0.0 | a1d282b36b6f3519aa1f3fc636f609c47dddb294 |
secured_ci.yml |
semgrep/semgrep-action |
v1.1.0 | 713efdd345f3035192eaa63f56867b88e63e4e5d |
secured_ci.yml |
actions/upload-pages-artifact |
v5.0.0 | fc324d3547104276b827a68afc52ff2a11cc49c9 |
docs.yml |
actions/deploy-pages |
v5.0.0 | cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 |
docs.yml |
Known gap — mermaid-render.yml:
[GAP: mermaid-render.yml uses actions/checkout@v4.3.0 as a tag-only pin,
not a SHA pin. Bring into compliance with Section 5.1 in the next
mermaid-render.yml changelog entry (CI-37).]
6. Permissions and Secrets Hygiene¶
Governing authority: GitHub Security Hardening for Actions — "Permissions for the GITHUB_TOKEN"; NIST SP 800-218 PW.2 (Review software for security vulnerabilities).
6.1 Principle of Least Privilege¶
Workflow-level permissions must be set to the minimum required. Default for all workflows in this repository:
yaml
permissions:
contents: read
pull-requests: read
security-events: write
Job-level permissions override workflow-level where a job requires elevated
access. Never grant write-all or omit the permissions: block entirely
(which defaults to the repository's maximum token permissions).
6.2 Secret Handling Rules¶
- Secrets must never appear in workflow
run:steps as literal values,echooutput, or environment variable debug output. - All secrets must be stored in GitHub Actions Encrypted Secrets or
GitHub Environments — never in workflow files,
.envfiles committed to the repo, or workflowenv:blocks as literals. - Secrets passed via
env:to a step are acceptable; secrets passed viawith:inputs to a third-party action require the action to be SHA-pinned per Section 5. - Required CI secrets for
secured_ci.yml:
| Secret | Purpose | Job |
|---|---|---|
CI_JWT_SECRET_KEY |
JWT signing for integration tests | integration-tests |
CI_REDIS_PASSWORD |
Redis auth for integration tests | integration-tests |
DEV_ADMIN_PASSWORD |
Seed admin user | integration-tests |
DEV_ANALYST_PASSWORD |
Seed analyst user | integration-tests |
DEV_OPERATOR_PASSWORD |
Seed operator user | integration-tests |
SEMGREP_APP_TOKEN |
Semgrep Cloud platform token | sast (optional) |
6.3 Secret Presence Verification¶
For any job that requires a secret to function correctly, add an explicit presence check as the first step:
yaml
- name: Verify required CI secrets are present
run: |
if [ -z "${{ secrets.MY_SECRET }}" ]; then
echo "ERROR: MY_SECRET is not configured."
exit 1
fi
7. Gate Order and Job Contract¶
The canonical job execution order for secured_ci.yml:
secrets-scan
├── dependency-audit ──────────────────────────────┐
│ ├── unit-tests
└── sast ──────────────────────────────────────────┘
└── integration-tests
└── container-scan
└── deploy-gate
dependency-review (PR only, independent)
7.1 Job Contract Requirements¶
Each job must satisfy:
| Field | Requirement |
|---|---|
name: |
Meaningful; matches the job's security function |
needs: |
Explicitly declared; never left implicit |
permissions: |
Scoped to minimum required |
| Gate type | Declared in a comment: # Gate type: hard or # Gate type: observational |
| Artifacts | Retention period explicitly set |
7.2 Gate Types¶
- Hard gate: Job fails and blocks the pipeline on finding. No
continue-on-error: trueon the gating step. - Observational gate: Produces a SARIF or report artifact but does not
block merge. Must use
continue-on-error: trueandif: always()on the upload step.
7.3 Artifact Retention Policy¶
| Artifact type | Minimum retention |
|---|---|
| Security scan results (SARIF, pip-audit) | 90 days |
| Test coverage reports | 30 days |
| SBOM (SPDX-JSON) | 365 days |
| Build logs | GitHub default (90 days) |
8. SARIF Seeding Policy¶
To prevent upload failures when a scan step exits non-zero before writing output, every SARIF file must be seeded with a valid empty document before the scan step that produces it.
Seed format:
```bash
echo '{"version":"2.1.0","$schema":"https://json.schemastore.org/sarif-2.1.0.json",
"runs":[{"tool":{"driver":{"name":"
.sarif ```
All SARIF uploads must use if: always() to ensure the upload runs regardless
of whether the preceding scan step passed or failed.
Stable SARIF filenames in this repository:
| Tool | Filename |
|---|---|
| Bandit (app) | bandit-app.sarif |
| Bandit (tests) | bandit-tests.sarif |
| Semgrep | semgrep.sarif |
| Trivy (container) | trivy-container.sarif |
9. Concurrency Policy¶
All workflows must declare a concurrency group to prevent redundant parallel runs. Cancel-in-progress must be enabled for all CI and docs workflows.
yaml
concurrency:
group: <prefix>-${{ github.ref }}
cancel-in-progress: true
Prefix conventions:
| Workflow | Group prefix |
|---|---|
secured_ci.yml |
ci |
docs.yml |
pages |
mermaid-render.yml |
mermaid |
Exception: Do not use cancel-in-progress: true for deployment workflows
that write to production environments — a cancelled in-progress deploy can
leave infrastructure in a partially applied state.
10. Runner and Environment Policy¶
- All jobs use
ubuntu-latestunless a specific OS dependency requires otherwise. Document any deviation with a comment stating the reason. - Python version is pinned explicitly in
setup-python(python-version: '3.11') — neverpython-version: 'latest'orpython-version: '3.x'. - Node version is pinned explicitly in
setup-node(node-version: '20'). - Docker base images are pinned to a specific version tag
(e.g.,
postgres:16-alpine,redis:7.4.3-alpine) — neverlatest.
11. Same-Commit Discipline¶
Any change to a CI/CD convention, workflow file, or action version must be accompanied by the relevant documentation update in the same commit.
Mandatory same-commit checklist:
- [ ] Relevant workflow file updated
- [ ] In-file changelog entry added (
CI-NNincremented, version bumped) - [ ] This file (
docs/ci-conventions.md) updated: - Section 4.1: current highest
CI-NN - Section 5.4: reference map if any action version changed
- [ ]
mkdocs.ymlnav updated if a new doc file was added
12. Gap Declaration Policy¶
Unresolved conventions, ambiguities, or missing definitions must be declared inline:
[GAP: description of what is not yet defined or verified]
A [GAP: ...] marker is not a placeholder to be filled with a plausible
answer. It remains until the responsible party provides the information needed
to close it. When a gap is closed, the resolution source is documented inline
and the marker is removed.
13. Changelog¶
| Version | Date | Change |
|---|---|---|
| 1.0.0 | 2026-05-25 | Initial creation; migrated content from .github/COMMIT_CONVENTION.md (archived); incorporated CI changelog, action pinning, SARIF seeding, gate order, permissions, secrets hygiene, concurrency, and runner policy |