Name the two kinds of pinned key: host-pinned and master-pinned

Pinning is one rule — strip the key from the branch layer — but the keys
fall into two groups by where they are meant to live: what only the
machine can know, and what belongs in the repository yet must not be
decided per branch. docker.enabled/network moves from the first group to
the second once the committed config carries it.

The KDoc says explicitly that the distinction is documentary, so nobody
looks for two mechanisms in stripPinned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-30 08:26:47 +02:00
co-authored by Claude Opus 5
parent 93bed2bd45
commit ef8b46b6e1
2 changed files with 30 additions and 5 deletions
+21 -5
View File
@@ -76,12 +76,28 @@ only, so a definition committed on one branch can never trigger builds of anothe
when its `branches` selector names one. when its `branches` selector names one.
A pinned set is always taken from the repo install/project config, because none of it A pinned set is always taken from the repo install/project config, because none of it
describes this branch's build: describes this branch's build.
Which of those two files is expected to carry a key gives the two names used throughout
this documentation.
**host-pinned** — only the machine can know it, and it never belongs in the repository:
- secrets: the whole `git` section; - secrets: the whole `git` section;
- host- and repository-side settings: the whole `server`, `gitea`, `executor`, and `watcher` sections; - the host's own addresses and ports: the whole `server` section.
- the container sandbox policy: `docker.enabled` and `docker.network`;
- the trust gate: `requirePullRequest`. **master-pinned** — it belongs in the repository, where changing it needs a review, but no
single branch may decide it:
- the repository-side settings: the whole `gitea`, `executor`, and `watcher` sections;
- the trust gate: `requirePullRequest`, and the Gitea status context: `statusContext`;
- the container sandbox policy: `docker.enabled` and `docker.network` — host-pinned as
long as only the host's configuration sets them, master-pinned once the committed
configuration does.
The distinction is documentary.
GitTally applies one rule: every pinned key is stripped from the branch layer, and the
value then resolves from whichever remaining layer sets it.
The names say where a key is meant to live, not how it is enforced.
This keeps a branch from reaching credentials, reporting statuses to another repository, This keeps a branch from reaching credentials, reporting statuses to another repository,
raising the global concurrency, disabling its own build container, changing its network raising the global concurrency, disabling its own build container, changing its network
@@ -328,7 +344,7 @@ Both parts combine as an intersection.
Settings: `buildCommand`, `cleanCommand`, `artifactDirs`, `stdoutLog`/`stderrLog`, `requirePullRequest`, `statusContext`, and `docker` with all its keys. Settings: `buildCommand`, `cleanCommand`, `artifactDirs`, `stdoutLog`/`stderrLog`, `requirePullRequest`, `statusContext`, and `docker` with all its keys.
A definition carries the complete description of its build; unset keys fall back to `builds.default` and then to GitTally's own defaults. A definition carries the complete description of its build; unset keys fall back to `builds.default` and then to GitTally's own defaults.
`requirePullRequest`, `statusContext`, `docker.enabled`, and `docker.network` are pinned: they are read from the repo install/project config even when a branch sets them in its own committed config, see [the branch layer](#the-branch-layer-a-branch-describes-its-own-ci). `requirePullRequest`, `statusContext`, `docker.enabled`, and `docker.network` are pinned (master-pinned, see [the branch layer](#the-branch-layer-a-branch-describes-its-own-ci)): they are read from the repo install/project config even when a branch sets them in its own committed config.
Inheritance from `builds.default` covers the settings only — the `trigger` block says when and where *this* build runs and is never inherited. Inheritance from `builds.default` covers the settings only — the `trigger` block says when and where *this* build runs and is never inherited.
Definitions are part of the branch layer: a branch may add its own and override those from the project config, for its own builds only. Definitions are part of the branch layer: a branch may add its own and override those from the project config, for its own builds only.
Because the inheritance is applied after all layers are merged, a build a branch invents still inherits the host's `builds.default` — its sandbox policy included, which is what keeps the pinning effective for a build the host has never heard of. Because the inheritance is applied after all layers are merged, a build a branch invents still inherits the host's `builds.default` — its sandbox policy included, which is what keeps the pinning effective for a build the host has never heard of.
@@ -125,6 +125,15 @@ class ConfigLoader(
* sections, the trust gate, and the docker sandbox policy — the latter two wherever * sections, the trust gate, and the docker sandbox policy — the latter two wherever
* they may appear, in a `builds` definition as well as in a legacy `branches` entry. * they may appear, in a `builds` definition as well as in a legacy `branches` entry.
* See [loadWithBranchLayer]. * See [loadWithBranchLayer].
*
* There is one rule here, not two: a pinned key is dropped from the branch layer and
* then resolves from whichever remaining layer sets it. The documentation still names
* two groups — *host-pinned* for what only the machine can know (`git`, `server`) and
* *master-pinned* for what belongs in the repository but must not be decided per
* branch (`gitea`, `executor`, `watcher`, `requirePullRequest`, `statusContext`).
* That distinction says where a key is meant to live, not how it is stripped, and it
* is not visible here: `docker.enabled`/`network` moves from the first group to the
* second as soon as the committed configuration carries them.
*/ */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
private fun stripPinned(branchLayer: Map<String, Any?>): Map<String, Any?> { private fun stripPinned(branchLayer: Map<String, Any?>): Map<String, Any?> {