diff --git a/README.md b/README.md index ef37198..934049a 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,12 @@ CI also runs `pnpm exec publint` and `pnpm exec attw --pack` after the build. It Both cosmiconfig majors are installed as dev dependencies under the aliases `cosmiconfig-9` and `cosmiconfig-10`, so `src/interop.integration.test.ts` exercises the loader and transform against each. `test/source` holds tests relocated from the project this package was extracted from, together with the domain they exercise, adapted onto the public API. They are the compatibility check for `extends` semantics. -CI selects its runner with `ExaDev/runner-fallback-action` (self-hosted fleet first, Blacksmith as fallback). The release job stays on a GitHub-hosted runner because npm trusted publishing needs one, and publishes with provenance through OIDC, with no stored token. The repository deploy key (the `RELEASE_DEPLOY_KEY` secret) is provisioned only for the semantic-release step: checkout gets no key and persists no credentials, and the key is written to the runner's temp directory just before that step, used over SSH with GitHub's pinned host key, and deleted afterwards. That keeps it out of git config and off disk while checkout, the setup actions, the install and the npm upgrade run, but code those earlier steps installed or configured (PATH, environment, `node_modules`, global npm) still runs with the key present, so it is not isolated from a compromised earlier step. `release.config.ts` sets an SSH `repositoryUrl`, so semantic-release pushes the release commit and tag as the deploy key, which a ruleset on `main` can list as a bypass actor; the default `GITHUB_TOKEN` cannot bypass a ruleset. +CI selects its runner with `ExaDev/runner-fallback-action` (self-hosted fleet first, Blacksmith as fallback). -Commits follow [Conventional Commits](https://www.conventionalcommits.org); semantic-release derives the version from them. +## Releases + +Releases are fully automated by semantic-release on every push to `main`; nothing is versioned or published by hand. Commits follow [Conventional Commits](https://www.conventionalcommits.org); semantic-release derives the version from them. The release job runs only after the commitlint, lint, typecheck and package jobs pass, analyses the commits since the last tag, and when one warrants a release it updates `CHANGELOG.md` and `package.json`, publishes to npm, tags the release, creates a GitHub Release and commits the changelog and version bump back to `main` as `chore(release)` with `[skip ci]`. Which commit types trigger which bump is defined once in `release.config.ts`, which commitlint also reads. + +The release job stays on a GitHub-hosted runner because npm trusted publishing needs one, and publishes with provenance through OIDC, with no stored token. The repository deploy key (the `RELEASE_DEPLOY_KEY` secret) is provisioned only for the semantic-release step: checkout gets no key and persists no credentials, and the key is written to the runner's temp directory just before that step, used over SSH with GitHub's pinned host key, and deleted afterwards. That keeps it out of git config and off disk while checkout, the setup actions, the install and the npm upgrade run, but code those earlier steps installed or configured (PATH, environment, `node_modules`, global npm) still runs with the key present, so it is not isolated from a compromised earlier step. `release.config.ts` sets an SSH `repositoryUrl`, so semantic-release pushes the release commit and tag as the deploy key, which a ruleset on `main` can list as a bypass actor; the default `GITHUB_TOKEN` cannot bypass a ruleset. + +`main` is protected by a ruleset that requires the Required Checks status, an up to date branch and linear history, and forbids deletion and non-fast-forward updates, so changes land through pull requests rebased onto `main`. The release commit and tag are pushed straight to `main` over SSH, which is why the deploy key is listed in the ruleset as a bypass actor.