REPO_POLICIES: fix the review findings on the compaction (#158)
check / check (push) Successful in 25s

Fixes the findings of the review of
#159.

- CI: "The workflow sets these:", so the four settings bind every
  repo's workflow again.
- node and yarn: a stage that needs no yarn copies no `yarn.lock`
  before it runs `script/bootstrap`, which is what the examples do.
- Dockerfile: names the stage to keep last, the one that ships.
- Version: "Without that check" replaces "Otherwise".
- The lists the review named are bulleted again, with the wording on
  `next`. The submodule examples are in their own sentence, as in both
  checklists.
- `TODO.md`: the entry carries the new size, 1,062 lines and 7,794
  words.

Model: fable-5-1
This commit is contained in:
2026-11-06 01:25:27 +00:00
parent 6dcacf673f
commit 2e98be476a
2 changed files with 89 additions and 39 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ fmt-check, and commit.
# Completed Steps # Completed Steps
- 2026-11-06: Compacted `prompts/REPO_POLICIES.md` from 1,145 lines and 8,427 - 2026-11-06: Compacted `prompts/REPO_POLICIES.md` from 1,145 lines and 8,427
words to 1,012 lines and 7,709 words words to 1,062 lines and 7,794 words
(https://git.eeqj.de/sneak/prompts/issues/158). Every rule keeps its meaning. (https://git.eeqj.de/sneak/prompts/issues/158). Every rule keeps its meaning.
The rules now sit under headings, the pinning rule first. Each rule that was The rules now sit under headings, the pinning rule first. Each rule that was
stated in several places is stated once. The Makefile copies of the Go and stated in several places is stated once. The Makefile copies of the Go and
+88 -38
View File
@@ -73,11 +73,15 @@ possible:
- Never force-push to `main`. - Never force-push to `main`.
- Never use `git add -A` or `git add .`. Always stage files explicitly by name. - Never use `git add -A` or `git add .`. Always stage files explicitly by name.
- **No build artifacts in version control.** Code-derived data must never be - **No build artifacts in version control.** Code-derived data must never be
committed if it can be avoided: compiled bundles, minified output, generated committed to the repository if it can be avoided. Code-derived data includes:
assets. The build process (e.g. Dockerfile, Makefile) should generate these at - compiled bundles
build time. Notable exception: Go protobuf generated files (`.pb.go`) ARE - minified output
committed, because repos need to work with `go get`, which downloads code but - generated assets
does not execute code generation.
The build process (e.g. Dockerfile, Makefile) should generate these at build
time. Notable exception: Go protobuf generated files (`.pb.go`) ARE
committed, because repos need to work with `go get`, which downloads code
but does not execute code generation.
# Entrypoints # Entrypoints
@@ -206,8 +210,9 @@ present.
Use `yarn`, not `npm`. Use `yarn`, not `npm`.
- `script/bootstrap` installs node, yarn and the node packages only when - `script/bootstrap` installs node, yarn and the node packages only when
`yarn.lock` is in the tree. A `Dockerfile` stage that needs no yarn therefore `yarn.lock` is in the tree. A `Dockerfile` stage that needs no yarn copies no
copies no `yarn.lock`. The Go and Python stages below are such stages. `yarn.lock` before it runs `script/bootstrap`, so `script/bootstrap` installs
no node and no yarn there. The Go and Python stages below are such stages.
- It installs the node packages with `yarn install --immutable`, which fails - It installs the node packages with `yarn install --immutable`, which fails
when a package's `checksum` is wrong, or when `package.json` is out of step when a package's `checksum` is wrong, or when `package.json` is out of step
with `yarn.lock`. with `yarn.lock`.
@@ -250,8 +255,14 @@ what the tool even is. The canonical form:
- compares the installed version against the pin over the **whole** version - compares the installed version against the pin over the **whole** version
token. A parser that stops at the first `-` reports `2.12.2` for a host token. A parser that stops at the first `-` reports `2.12.2` for a host
running `2.12.2-rc1` and skips the install. running `2.12.2-rc1` and skips the install.
- treats `--version` output that is absent, non-zero, empty or unrecognised as a - treats `--version` output as a mismatch when it is any of these:
mismatch. A failure then causes a redundant install, never a skipped one. - absent
- non-zero
- empty
- unrecognised
So a failure causes a redundant install, never a skipped one.
- after installing, re-resolves the binary the way callers do: `hash -r`, then - after installing, re-resolves the binary the way callers do: `hash -r`, then
`PATH`, not the directory the installer wrote to. A shadowing binary can hide `PATH`, not the directory the installer wrote to. A shadowing binary can hide
an install that reported success. In that case it fails and names the resolved an install that reported success. In that case it fails and names the resolved
@@ -289,7 +300,8 @@ docker build --build-arg CACHEBUST="$(date +%s)-$$" \
two gates are always invoked by name here, and why the final stage carries a two gates are always invoked by name here, and why the final stage carries a
`COPY --from=` of a harmless file from each of them. Without those copies, a `COPY --from=` of a harmless file from each of them. Without those copies, a
plain `docker build .` builds the last stage's chain alone and exits 0 having plain `docker build .` builds the last stage's chain alone and exits 0 having
linted and tested nothing. Keep the final stage last. If you add a stage after linted and tested nothing. Keep the stage that ships last: the runtime image,
or the development environment in a non-server repo. If you add a stage after
it, give it the same two copies. it, give it the same two copies.
- **The gate builds write no image.** With `--output type=cacheonly` the phase - **The gate builds write no image.** With `--output type=cacheonly` the phase
runs and a failing step fails the build, but the result is not exported. runs and a failing step fails the build, but the result is not exported.
@@ -352,8 +364,12 @@ step that also installs anything else comes after `ARG CACHEBUST` too. A
`script/bootstrap` that also installs node packages is one. `script/bootstrap` that also installs node packages is one.
- A new value reruns every `RUN` step after that line; `COPY` steps are compared - A new value reruns every `RUN` step after that line; `COPY` steps are compared
by content and need nothing. Docker keeps the layers above the line until the by content and need nothing. Docker keeps the layers above the line until one
base image, their own lines or the files they copy change. of these changes:
- the base image
- their own lines
- the files they copy
- A stage without the `ARG` line is served from cache on an unchanged tree, so a - A stage without the `ARG` line is served from cache on an unchanged tree, so a
repository re-vendors the scripts and adds the `ARG` lines in one commit. repository re-vendors the scripts and adds the `ARG` lines in one commit.
- The scripts do not pass `--no-cache`. That flag reruns every step too, but it - The scripts do not pass `--no-cache`. That flag reruns every step too, but it
@@ -529,17 +545,26 @@ Key points:
above `ARG CACHEBUST`, so check builds keep them.** The steps are above `ARG CACHEBUST`, so check builds keep them.** The steps are
`COPY script/ script/`, `COPY Pipfile Pipfile.lock ./` and `COPY script/ script/`, `COPY Pipfile Pipfile.lock ./` and
`RUN script/bootstrap`, with `ARG CACHEBUST` right after them. Docker keeps what `RUN script/bootstrap`, with `ARG CACHEBUST` right after them. Docker keeps what
that `RUN` installed until the base image, `script/`, `Pipfile`, `Pipfile.lock` that `RUN` installed until one of these changes:
or a line up to and including the `RUN` changes.
- the base image
- `script/`
- `Pipfile`
- `Pipfile.lock`
- a line up to and including the `RUN`
The tests run after `ARG CACHEBUST`, on every check build. In every other stage, The tests run after `ARG CACHEBUST`, on every check build. In every other stage,
a step that installs Python packages comes after `ARG CACHEBUST`. a step that installs Python packages comes after `ARG CACHEBUST`.
In that step, `script/bootstrap`: In that step, `script/bootstrap`:
- installs nothing beyond system packages, pipenv and its dependencies, and the - installs nothing beyond these:
packages from `Pipfile.lock`. If it installs anything else there, the step - system packages
goes after `ARG CACHEBUST`. - pipenv and its dependencies
- the packages from `Pipfile.lock`
If it installs anything else there, the step goes after `ARG CACHEBUST`.
- installs no Python: the tests use the distribution's Python, which a step - installs no Python: the tests use the distribution's Python, which a step
above it installs. above it installs.
- puts pipenv in a virtual environment at `$HOME/.pipenv`, because Debian's - puts pipenv in a virtual environment at `$HOME/.pipenv`, because Debian's
@@ -553,9 +578,11 @@ In that step, `script/bootstrap`:
that lists each of them with `==` and its `--hash`. pip refuses to install a that lists each of them with `==` and its `--hash`. pip refuses to install a
dependency the file does not list. dependency the file does not list.
- then runs `pipenv install --deploy`, with `--dev` when the tests need the - then runs `pipenv install --deploy`, with `--dev` when the tests need the
development packages. `--deploy` installs only what `Pipfile.lock` names, development packages. `--deploy` does all of these:
checks every package against the lockfile's hashes, and fails when the - installs only what `Pipfile.lock` names
lockfile is out of date with `Pipfile`. - checks every package against the lockfile's hashes
- fails when the lockfile is out of date with `Pipfile`
- sets `PIP_ONLY_BINARY=:all:` for both installs, and pipenv passes it on to - sets `PIP_ONLY_BINARY=:all:` for both installs, and pipenv passes it on to
pip. A package with no ready-built wheel for the image then stops the install. pip. A package with no ready-built wheel for the image then stops the install.
Without the setting, pip builds that package from source and downloads the Without the setting, pip builds that package from source and downloads the
@@ -658,8 +685,9 @@ canonical Go `Dockerfile` above shows:
stream keeps the sender's file owners, and git refuses a checkout owned by stream keeps the sender's file owners, and git refuses a checkout owned by
another user, so the version would come out empty. another user, so the version would come out empty.
- The build fails if the context carries `.git` and the version still comes out - The build fails if the context carries `.git` and the version still comes out
empty, `dev` or `unknown`. Otherwise a missing `git` or an unreadable checkout empty, `dev` or `unknown`. Without that check, a missing `git` or an
would leave `-X main.Version=` empty, and the build would still exit 0. unreadable checkout would leave `-X main.Version=` empty, and the build would
still exit 0.
- A plain `docker build .` with no build arguments must succeed; a Dockerfile - A plain `docker build .` with no build arguments must succeed; a Dockerfile
that refuses an empty build argument drops that refusal and keeps the that refuses an empty build argument drops that refusal and keeps the
argument. argument.
@@ -677,11 +705,11 @@ canonical Go `Dockerfile` above shows:
- that of a submodule keeping its own `.git` directory - that of a submodule keeping its own `.git` directory
`git describe` does not need them, and each can hold a credential: a password in `git describe` does not need them, and each can hold a credential: a password in
a remote URL, or the token the CI checkout step stores there. A submodule whose a remote URL, or the token the CI checkout step stores there. Some submodule
name has a `config` segment (`config`, `deploy/config`, `config/lib`) loses its names have a `config` segment, as in `config`, `deploy/config` or `config/lib`.
whole git directory to `**/.git/modules/**/config`, and Go's version stamping Such a submodule loses its whole git directory to `**/.git/modules/**/config`,
then fails the build. Give it a name without that segment and Go's version stamping then fails the build. Give it a name without that
(`git submodule add --name`). segment (`git submodule add --name`).
`script/docker` and `script/cibuild` pass the version they compute on the host; `script/docker` and `script/cibuild` pass the version they compute on the host;
it takes precedence. They do this byte-identically across repos: it takes precedence. They do this byte-identically across repos:
@@ -711,7 +739,7 @@ docker build --build-arg CACHEBUST="$(date +%s)-$$" \
Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that runs Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that runs
`script/cibuild` on push. The workflow's only other step checks out the repo. `script/cibuild` on push. The workflow's only other step checks out the repo.
The canonical `.gitea/workflows/check.yml` sets these: The workflow sets these:
- `persist-credentials: false` on the checkout step. `script/cibuild` needs no - `persist-credentials: false` on the checkout step. `script/cibuild` needs no
token, and without that setting the checkout leaves the job's token in token, and without that setting the checkout leaves the job's token in
@@ -910,8 +938,14 @@ including, without limitation, all of the following:
to an explicit allowlist of known origins. Wildcard (`*`) is acceptable only to an explicit allowlist of known origins. Wildcard (`*`) is acceptable only
for public, unauthenticated read-only APIs. for public, unauthenticated read-only APIs.
- **Error handling:** Internal errors must never leak implementation details to - **Error handling:** Internal errors must never leak implementation details to
the client: stack traces, SQL queries, file paths. Return generic error the client. Those details include:
messages in production; detailed errors only when `DEBUG` is enabled. - stack traces
- SQL queries
- file paths
Return generic error messages in production; detailed errors only when
`DEBUG` is enabled.
- **TLS:** Services never terminate TLS directly. They are always deployed - **TLS:** Services never terminate TLS directly. They are always deployed
behind a TLS-terminating reverse proxy. The service itself listens on plain behind a TLS-terminating reverse proxy. The service itself listens on plain
HTTP. However, HSTS headers and `Secure` cookie flags must still be set by the HTTP. However, HSTS headers and `Secure` cookie flags must still be set by the
@@ -937,10 +971,17 @@ the binary.
`README.md` is the primary documentation. Required sections: `README.md` is the primary documentation. Required sections:
- **Description**: First line must include the project name, purpose, category - **Description**: First line must include:
(web server, SPA, CLI tool, etc.), license and author. Example: "µPaaS is an - the project name
MIT-licensed Go web application by @sneak that receives git-frontend webhooks - purpose
and deploys applications via Docker in realtime." - category (web server, SPA, CLI tool, etc.)
- license
- author
Example: "µPaaS is an MIT-licensed Go web application by @sneak that
receives git-frontend webhooks and deploys applications via Docker in
realtime."
- **Getting Started**: Copy-pasteable install/usage code block. - **Getting Started**: Copy-pasteable install/usage code block.
- **Entrypoints**: Opens by stating that the repo adheres to the - **Entrypoints**: Opens by stating that the repo adheres to the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) [Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
@@ -958,10 +999,19 @@ the binary.
# Repository layout # Repository layout
Avoid putting files in the repo root unless necessary. Root should contain only Avoid putting files in the repo root unless necessary. Root should contain only
project-level config files: `README.md`, `AGENTS.md`, `Makefile`, `Dockerfile`, project-level config files:
`LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md` and
language-specific config. Everything else goes in a subdirectory. Canonical - `README.md`
subdirectory names: - `AGENTS.md`
- `Makefile`
- `Dockerfile`
- `LICENSE`
- `.gitignore`
- `.editorconfig`
- `REPO_POLICIES.md`
- language-specific config
Everything else goes in a subdirectory. Canonical subdirectory names:
- `bin/` — executable scripts and tools - `bin/` — executable scripts and tools
- `cmd/` — Go command entrypoints; thin only: one `main.go` per binary whose - `cmd/` — Go command entrypoints; thin only: one `main.go` per binary whose