Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ bash scripts/e2e/gitlab.sh all # every supported GitLab with a runner; about
CI runs the Gitea and Forgejo tests on every pull request; a maintainer adds the label
`e2e-gitlab` to run the GitLab ones. A change to the image, the Action or what the hub
template's CI files rely on also gets a run of the template's pipelines (`--template`).
GitHub's tests run against a fake of its API; the live ones need a sandbox organisation.
GitHub's tests run against a fake of its API; on github.com touchmark runs for real in the
bedrock-python hub.
[End-to-end tests](https://bedrock-python.github.io/touchmark/project/e2e/) has the
details.

Expand Down
20 changes: 10 additions & 10 deletions docs/project/e2e.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/project/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,18 @@ When something fails:

## After a release: the template and the hubs

The hub template (`bedrock-python/engineering-assets-template`) and the bedrock-python hub name touchmark by commit and image digest. Until the first release they carry placeholders marked `TODO(release)`: `bedrock-python/touchmark@0000… # v0.1.0` in `.github/workflows`, and `ghcr.io/bedrock-python/touchmark:0.1.0@sha256:0000…` in `.gitlab-ci.yml` and `.gitea/workflows`. A hub created from a template with placeholders fails at the Action's step or at the image pull.
The hub template (`bedrock-python/engineering-assets-template`) and the bedrock-python hub name touchmark by commit and image digest: the Action in `.github/workflows` by the commit of its tag, with `# vX.Y.Z` after it, and in the template the image as `ghcr.io/bedrock-python/touchmark:X.Y.Z@<digest>` in `.gitlab-ci.yml` and `.gitea/workflows`. Dependabot proposes the next release of the Action and Renovate the image's, after their cooldown; a release of our own can be pinned at once:

1. In each of them, replace every placeholder with the release: the tag's commit (`git rev-parse vX.Y.Z^{commit}`, with `# vX.Y.Z` after it) and the image `ghcr.io/bedrock-python/touchmark:X.Y.Z@<digest>`, the digest from `docker buildx imagetools inspect ghcr.io/bedrock-python/touchmark:X.Y.Z --format '{{ .Manifest.Digest }}'`, and delete the `TODO(release)` lines. Afterwards Dependabot moves the Action and Renovate the image.
2. Run `bash scripts/validate.sh --release` there: it fails while a placeholder is left.
1. In each of them, pin the release: the tag's commit (`git rev-parse vX.Y.Z^{commit}`) and the image's digest from `docker buildx imagetools inspect ghcr.io/bedrock-python/touchmark:X.Y.Z --format '{{ .Manifest.Digest }}'`.
2. Run `bash scripts/validate.sh --release` there: it checks that touchmark is pinned to a release everywhere.
3. Run the template's pipelines against the published image ([e2e.md](e2e.md), "The hub template"): `scripts/e2e/gitea.sh` and `scripts/e2e/gitlab.sh` with `--template <template> --touchmark-image ghcr.io/bedrock-python/touchmark@<digest> --run TestTemplate`, and the GitHub dry run `TestTemplateWorkflow` with `TOUCHMARK_E2E_TEMPLATE` in the Docker run of the suite.
4. Merge the change through a pull request in each repository.

Once, when the template is first published:

- *Settings → General*: tick **Template repository**.
- *Settings → Actions → General*: **Disable actions**. The template's workflows are a hub's: they deliver from the default branch and check every pull request of a hub, and in the template itself, with the placeholder `id` and no keys, every run would fail (`check` rejects the placeholder by design). touchmark's own CI checks the template instead (`ci.yml`, job `template`). Dependabot's version updates still open pull requests there.
- A GitLab mirror `gitlab.com/bedrock-python/engineering-assets-template`: create it, and turn **CI/CD** off in its *Settings → General → Visibility, project features, permissions*, for the same reason. Keep it in sync from outside the template: a workflow placed in the template is copied into every hub created from it. Offer it in the template's README and in [GitLab](../getting-started/gitlab.md) only once it exists; until then both give the template's URL on GitHub to import.
- There is no GitLab mirror of the template: on GitLab a hub imports the template from its URL on GitHub ([GitLab](../getting-started/gitlab.md)).

## Try a release locally

Expand Down
12 changes: 6 additions & 6 deletions docs/project/threat-model.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions internal/platform/github/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import (

// basicUser is the user name of git's Basic credentials: GitHub takes
// "x-access-token" with an installation token, and a personal access token
// as the password whatever the name (unverified for fine-grained tokens:
// the sandbox checks it). The CLI and the driver register the Basic form
// as the password whatever the name (unverified for fine-grained
// tokens). The CLI and the driver register the Basic form
// of this user for masking.
const basicUser = "x-access-token"

Expand Down
8 changes: 4 additions & 4 deletions internal/platform/github/ghfake/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
// It models only what touchmark uses, and each behavior is taken from
// GitHub's documentation, from read-only calls to public github.com data,
// or assumed where neither settles it; the table below names the source
// of every one. Assumed behaviors are what the live GitHub sandbox must
// confirm (docs/project/e2e.md, live sandbox scenarios); a test that
// depends on one of them proves only the fake.
// of every one. Assumed behaviors are what only a live GitHub can
// confirm (docs/project/e2e.md, what only a live GitHub confirms); a
// test that depends on one of them proves only the fake.
//
// # Layout
//
Expand Down Expand Up @@ -46,7 +46,7 @@
// Sources: "docs" (docs.github.com or the public schema, read 2026-09-24
// to 2026-09-29), "observed" (read-only calls to public repositories on
// 2026-09-29), "reports" (public issue reports, not documentation), and
// "assumed" (needs the live sandbox).
// "assumed" (needs a live GitHub).
//
// Apps, JWTs and tokens
// JWT RS256; iat not ahead, exp ahead and <= 10 min; docs apps/creating-github-apps/authenticating-with-a-github-app/
Expand Down
2 changes: 1 addition & 1 deletion internal/platform/github/ghfake/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ func getOrg(c *call) response {
// type (all, public, private, forks, sources, member) and sorted by
// created (newest first), updated, pushed or full_name (A to Z). An
// installation token also sees public repositories its installation does
// not cover (assumed; needs the live sandbox). The user forms list public
// not cover (assumed; needs a live GitHub). The user forms list public
// repositories only, whoever asks (docs:
// rest/repos/repos#list-repositories-for-a-user, "Lists public
// repositories for the specified user").
Expand Down
3 changes: 1 addition & 2 deletions internal/platform/github/ghfake/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ func (s *Server) addAccount(login, typ string, plan Plan) (*account, error) {

// noreplyDomain is the domain of noreply commit addresses: github.com's,
// or users.noreply.<host> on GHES (assumed: GHES documents the noreply
// address without its form; the sandbox or a GHES administrator confirms
// it).
// address without its form; a GHES administrator confirms it).
func (s *Server) noreplyDomain() string {
if s.flavor != GHES {
return "users.noreply.github.com"
Expand Down
4 changes: 2 additions & 2 deletions internal/platform/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
// GHES 3.19; GET /hash-algorithm exists on github.com and GHE.com, not
// in GHES 3.19.
//
// What only a live GitHub sandbox can confirm is listed in
// docs/project/e2e.md (live sandbox scenarios) and marked "unverified"
// What only a live GitHub can confirm is listed in docs/project/e2e.md
// (what only a live GitHub confirms) and marked "unverified"
// where the code depends on it.
//
// The driver never retries or paces (internal/throttle does), never knows
Expand Down
3 changes: 1 addition & 2 deletions internal/platform/github/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,7 @@ func (c *client) toAccount(u *apiUser) platform.Account {
// noreply is the id-based noreply address of an account: users.noreply.
// github.com on github.com
// (https://docs.github.com/account-and-profile/reference/email-addresses-reference),
// users.noreply.<host> elsewhere (unverified on GHE.com and GHES: the
// sandbox and a GHES administrator confirm it).
// users.noreply.<host> elsewhere (unverified on GHE.com and GHES).
func (c *client) noreply(id int64, login string) string {
host := c.host
if name, _, err := net.SplitHostPort(host); err == nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/platform/github/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ func (d *reader) Preflight(ctx context.Context, r platform.Repo, branches []stri
// with the App on its bypass list lets it push as if the rule were not
// there), "pull_requests_only" and "never" keep it, and so does a ruleset
// the writer cannot read. The installation token the writer reads with
// acts as the App, as its per-target token does (unverified: the sandbox
// confirms that a bypassing App reads "always"). The default branch's
// acts as the App, as its per-target token does (unverified: only a live
// GitHub shows that a bypassing App reads "always"). The default branch's
// rules stay: the person who merges meets them, not the writer.
func (w *writer) Preflight(ctx context.Context, r platform.Repo, branches []string) (platform.Rules, error) {
rules, err := w.c.rules(ctx, r, branches, true)
Expand Down
Loading