{
  "uid": "cogitave.learn.open-your-first-pull-request.write-a-signed-conventional-commit",
  "kind": "moduleUnit",
  "href": "/modules/open-your-first-pull-request/write-a-signed-conventional-commit/",
  "title": "Write a signed Conventional Commit",
  "summary": "",
  "type": null,
  "products": [],
  "roles": [],
  "levels": [],
  "subjects": [],
  "headings": [
    "The Conventional Commit shape",
    "Sign the commit"
  ],
  "source": "A commit is evidence. Its message and its signature are both read later - by a\nreviewer, by the changelog tooling, and by an auditor - so both have a required\nshape.\n\n## The Conventional Commit shape\n\nEvery commit message follows **Conventional Commits 1.0.0**:\n`<type>[scope][!]: <desc>`. This is mandatory everywhere and enforced by\ncommitlint. The full rule is the\n[commits-versioning standard](../../../../standards/docs/standards/commits-versioning.md);\nthe parts you need for a first commit are:\n\n- **type** (required) - what kind of change it is. `feat` (a new feature, bumps\n  MINOR) and `fix` (a bug fix, bumps PATCH) are the two you will reach for most.\n  The rest are `build`, `chore`, `ci`, `docs`, `style`, `refactor`, `perf`,\n  `test`, and `revert`.\n- **scope** (optional) - the area touched, in parentheses: `fix(parser):`.\n- **breaking change** - append `!` after the type/scope, and/or add a\n  `BREAKING CHANGE:` footer. Either marks it, and it bumps MAJOR.\n- **desc** - a short, imperative summary in English.\n\n```text\nfix(parser): reject empty capability grant\n\nThe grant validator accepted an empty scope list, which read as\n\"no capability\" but was stored as \"all\". Fail closed instead.\n```\n\ncommitlint runs this check in two places: the `commit-msg` hook locally and again\nin CI on your PR commits and title. A message that does not parse is rejected, so\nget it right at commit time rather than after the push.\n\n## Sign the commit\n\nThe commit must be **SSH-signed** so GitHub reports it as **Verified**. SSH signing\nis mandatory across the estate - it is the lowest-friction path to a verified\ncommit, and the org branch ruleset **requires signed commits**, so an unsigned\ncommit is rejected outright. This is set out in the\n[hooks-precommit standard](../../../../standards/docs/standards/hooks-precommit.md).\n\nConfigure signing once, then let Git sign every commit:\n\n```bash\ngit config --global gpg.format ssh\ngit config --global user.signingkey ~/.ssh/id_ed25519.pub\ngit config --global commit.gpgsign true\n```\n\n> [!WARNING]\n> Never disable signing to make a step easier - not with `--no-gpg-sign`, not by\n> flipping `commit.gpgsign` off. It is a non-negotiable rule of the floor\n> ([AGENTS.md](../../../../../AGENTS.md), rule 3); the ruleset rejects the push\n> anyway, and an unsigned commit has no non-repudiation lineage.\n\nWith the message shaped and the commit signed, you have the artifact. The next\nunit takes it through the gates and into a pull request.\n",
  "partOf": "cogitave.learn.open-your-first-pull-request",
  "durationInMinutes": 6,
  "quiz": null
}