REPO_POLICIES.md now carries the canonical script/bootstrap snippet for Go repos alongside the .golangci.yml bullet, where the pinned linter version already lives. The guard it replaces, `if missing golangci-lint; then go install ...; fi`, tests PATH presence and never version, so on any already-provisioned machine the pin is inert and a version bump is a no-op. The Dockerfile installs unconditionally into a clean image, so CI and local then disagree about what the linter is: a local `make check` green while `make docker` rejects the same commit, and a container run surfacing findings the host run cannot see. Comparing versions alone is not enough. `go install` writes to GOBIN (or GOPATH/bin) while callers resolve through PATH, so a shadowing binary earlier in PATH lets the install succeed and change nothing a caller ever sees, while bootstrap prints success. The canonical form therefore compares the installed version against the pin, re-resolves through PATH after installing and asserts the pin, failing non-zero and naming the shadowing path when it does not, and treats any unparseable --version output as a mismatch so the failure direction is a redundant install rather than a skipped one. The policy text states each of those as a requirement rather than leaving them implicit in the code, records why the commit-pinned `go install` ref satisfies the hash-pinning rule (a commit hash is not a mutable tag, and the go command verifies the module against the checksum database), and requires that any change to this logic be validated with a negative control run against a shadowing binary, because a control without one passes against the naive implementation too. The node and yarn handling described earlier in the document is untouched. Verified by extracting the snippet to a scratch harness with fake `go` and both fake and real golangci-lint binaries: shadowing fails loudly and names the path while the naive compare-then-install form reports success with the stale 2.7.2 still resolved; a wrong version at the install target is replaced; garbage, empty and non-zero --version output all reinstall; the matching case runs zero installs. The block in the document is byte-identical to the one exercised.
32 KiB
title, last_modified
| title | last_modified |
|---|---|
| Repository Policies | 2026-08-09 |
This document covers repository structure, tooling, and workflow standards. Code style conventions are in separate documents:
- Code Styleguide (general, bash, Docker)
- Go
- JavaScript
- Python
- Go HTTP Server Conventions
-
Cross-project documentation (such as this file) must include
last_modified: YYYY-MM-DDin the YAML front matter so it can be kept in sync with the authoritative source as policies evolve. -
ALL external references must be pinned by cryptographic hash. This includes Docker base images, Go modules, npm packages, GitHub Actions, and anything else fetched from a remote source. Version tags (
@v4,@latest,:3.21, etc.) are server-mutable and therefore remote code execution vulnerabilities. The ONLY acceptable way to reference an external dependency is by its content hash (Docker@sha256:..., Go module hash ingo.sum, npm integrity hash in lockfile, GitHub Actions@<commit-sha>). No exceptions. This also means nevercurl | bashto install tools like pyenv, nvm, rustup, etc. Instead, download a specific release archive from GitHub, verify its hash (hardcoded in the Dockerfile or script), and only then install. Unverified install scripts are arbitrary remote code execution. This is the single most important rule in this document. Double-check every external reference in every file before committing. There are zero exceptions to this rule. -
Every repo with software must have a root
Makefilewith these targets:make bootstrap,make setup,make test,make lint,make fmt(writes),make fmt-check(read-only),make check(runstest,lint,fmt-check),make docker, andmake hooks(installs pre-commit hook). A model Makefile is athttps://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile. -
Repos follow the Scripts to Rule Them All pattern: the implementation of each Makefile target lives in an executable script in
script/(script/bootstrap,script/setup,script/test,script/lint,script/fmt,script/fmt-check,script/check,script/docker), and the Makefile targets are thin shims that call them. The scripts must be POSIX sh (#!/bin/sh,set -eu, no bashisms) so they run in minimal containers (e.g. alpine images have no bash); locate the repo root with$(cd "$(dirname "$0")/.." && pwd -P)andcdthere before acting. From the standard's canonical set we usebootstrap,setup(make the repo ready for development after a fresh clone: runsbootstrap, theninstall-precommit, plus any repo-specific initialization),test, andcibuild.script/bootstrapinstalls all dependencies idempotently and assumes nothing is present: base tools come from nix, apt, brew, or apk (detected in that order; apt runs noninteractive). For node it uses the installed node if present; otherwise it installs a PINNED node version via nvm, first installing nvm itself if missing — from a hash-verified GitHub release archive (nevercurl | sh), with bash installed as an explicit prerequisite since nvm requires bash. yarn is then pinned viacorepack prepare yarn@<version> --activate. Never install "latest" or "lts"; always exact versions.script/cibuildruns the CI build: it changes to the repo root and runsdocker build --build-arg CHECK_EPOCH="$epoch" ., whereepochis a per-invocation nonce (see theCHECK_EPOCHrule below); the Gitea workflow calls it. Four further scripts are our own extensions to the standard:script/checkrunsscript/test,script/lint, andscript/fmt-check;script/precommitis what the git pre-commit hook runs, and it callsscript/check;script/install-precommitinstalls the git pre-commit hook (themake hookstarget shims to it); andscript/projectname(literally that filename) simply outputs the project's name. Scripts that need the name callscript/projectname— e.g.script/dockerassembles its image tag from it — so those scripts stay byte-identical across all repos. Repo-type-specific pre-commit extras (e.g.go mod tidyverification in Go repos) belong inscript/precommit, not in the hook itself. Model scripts are athttps://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>. The README must document the provided scripts in an Entrypoints section (see the README requirements below). -
Always use Makefile targets (
make fmt,make test,make lint, etc.) instead of invoking the underlying tools directly. The Makefile is the single source of truth for how these operations are run. -
The Makefile is authoritative documentation for how the repo is used. Beyond the required targets above, it should have targets for every common operation: running a local development server (
make run,make dev), re-initializing or migrating the database (make db-reset,make migrate), building artifacts (make build), generating code, seeding data, or anything else a developer would do regularly. If someone checks out the repo and typesmake<tab>, they should see every meaningful operation available. A new contributor should be able to understand the entire development workflow by reading the Makefile. -
Every repo should have a
Dockerfile. All Dockerfiles must runmake checkas a build step so the build fails if the branch is not green — which requiresARG CHECK_EPOCHand its guard in every stage containing a check-runningRUN, per theCHECK_EPOCHrule below. Without them a Dockerfile satisfies this criterion while its check layers are served from cache, so the build cannot fail on a branch that is not green. For non-server repos, the Dockerfile should bring up a development environment and runmake check. For server repos,make checkshould run as an early build stage before the final image is assembled. Dockerfiles install development prerequisites by runningscript/bootstraprather than duplicating installs inline; COPYscript/and the dependency manifests (package.json+yarn.lock,go.mod+go.sum, etc.) before running it so the bootstrap layer stays cached until dependencies change. -
Every check-running
RUNmust be cache-busted withCHECK_EPOCH. Docker invalidates aCOPYlayer only when the copied content changes, so on an unchanged tree theRUN make checklayer is served from cache, the suite never runs, and the build still exits 0. A sub-seconddocker buildreporting success is a cache hit, not a result. The canonical form, in every stage containing a check-runningRUN:ARG CHECK_EPOCH RUN [ -n "$CHECK_EPOCH" ] || exit 1 RUN echo "check epoch: ${CHECK_EPOCH}" && make checkand in both
script/cibuildandscript/docker:epoch="$(date +%s%N)$$" docker build --build-arg CHECK_EPOCH="$epoch" .All four elements are load-bearing; none is optional, and each guards a failure mode that otherwise fails green:
ARGis stage-scoped, so a single declaration leaves the other check stages frozen while the fix reviews as complete. Declare it in every stage that runs checks, immediately above the first suchRUN.- Expand the value into the command. This makes the cache miss contractual
rather than dependent on BuildKit's handling of an unreferenced
ARG, and it puts the epoch in the build log. The guard is itself value-keyed, for the same reason: it references$CHECK_EPOCH, so BuildKit renders the epoch into that layer's description (rendered asRUN [ -n "<epoch>" ] || exit 1) and re-runs it whenever the value changes. Each stage therefore has two independent invalidation points, and the guard always precedes the checkRUN. Keep both: the expansion is defence in depth, and it is what makes the epoch visible in the build output. - The
[ -n ... ]guard is required: an unsetARGis empty, and empty is a stable cache key, so without it a baredocker build .still produces the false green. Failed steps are never cached, so the guard fails on every such invocation, loudly. A baredocker build .failing is by design. - Assign
epoch=on its own line, never inline in the--build-argargument: a failing command substitution inside an argument does not tripset -e, so the inline form silently degrades to an empty constant. The$$suffix is required because busyboxdatedrops%Nand exits 0, so on an alpine host the epoch would degrade to second granularity and concurrent invocations would collide.
This invalidates the check layers and everything after them while leaving
go mod download,script/bootstrap, and the pinned toolchain install cached, so it does not push against the five-minute Docker build ceiling. Blanket--no-cachealso works but is wasteful and can blow that ceiling. -
Dockerfiles must use a separate lint stage for fail-fast feedback. Go repos use a multistage build where linting runs in an independent stage based on the
golangci/golangci-lintimage (pinned by hash). This stage runsmake fmt-checkandmake lintbefore the full build begins. The build stage then declares an explicit dependency on the lint stage viaCOPY --from=lint /src/go.sum /dev/null, which forces BuildKit to complete linting before proceeding to compilation and tests. This ensures lint failures surface in seconds rather than minutes, without blocking on dependency download or compilation in the build stage.The standard pattern for a Go repo Dockerfile is:
# Lint stage — fast feedback on formatting and lint issues # golangci/golangci-lint:v2.x.x, YYYY-MM-DD FROM golangci/golangci-lint@sha256:... AS lint WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . ARG CHECK_EPOCH RUN [ -n "$CHECK_EPOCH" ] || exit 1 RUN echo "check epoch: ${CHECK_EPOCH}" && make fmt-check RUN make lint # Build stage # golang:1.x-alpine, YYYY-MM-DD FROM golang@sha256:... AS builder WORKDIR /src # Force BuildKit to run the lint stage before proceeding COPY --from=lint /src/go.sum /dev/null COPY go.mod go.sum ./ RUN go mod download COPY . . ARG CHECK_EPOCH RUN [ -n "$CHECK_EPOCH" ] || exit 1 RUN echo "check epoch: ${CHECK_EPOCH}" && make test ARG VERSION=dev RUN CGO_ENABLED=0 go build -trimpath \ -ldflags="-s -w -X main.Version=${VERSION}" \ -o /app ./cmd/app/ # Runtime stage FROM alpine@sha256:... COPY --from=builder /app /usr/local/bin/app ENTRYPOINT ["app"]Key points:
- The lint stage uses the
golangci/golangci-lintimage directly (it includes both Go and the linter), so there is no need to install the linter separately. COPY --from=lint /src/go.sum /dev/nullis a no-op file copy that creates a stage dependency. BuildKit runs stages in parallel by default; without this line, the build stage would not wait for lint to finish and a lint failure might not fail the overall build.- Re-prove that ordering on a warm cache after adopting
CHECK_EPOCH. The cache-bust turns this no-opCOPYinto a content-cache hit, so an ordering guarantee established on a cold cache does not automatically carry over; it has to be re-checked warm. This was re-proved in another repo in the org that uses the same file-dependency trick (there with a marker file in place ofgo.sum), and the ordering held. It has not been verified in this repo, which is single-stage and has no lint stage to order against. Any repo relying on a file-dependency trick for stage ordering should re-check it warm after adopting the bust rather than assuming this result transfers. - If the project uses
//go:embeddirectives that reference build artifacts (e.g. a web frontend compiled in a separate stage), the lint stage must create placeholder files so the embed directives resolve. Example:RUN mkdir -p web/dist && touch web/dist/index.html web/dist/style.css. The lint stage should not depend on the actual build output — it exists to fail fast. - If the project requires CGO or system libraries for linting (e.g.
vips-dev), install them in the lint stage withapk add. - The build stage runs
make testafter compilation setup. Tests run in the build stage, not the lint stage, because they may require compiled artifacts or heavier dependencies. ARG CHECK_EPOCHappears in both stages, becauseARGis stage-scoped: declaring it only in the lint stage leavesmake testfrozen at the last cached result. In each stage the guard sits immediately below theARGso a baredocker build .fails instead of reusing the empty cache key, and the value is expanded into the first checkRUNso the cache miss does not rely on BuildKit's unreferenced-ARGhandling. Both of those lines reference$CHECK_EPOCH, so both are value-keyed: each stage is invalidated at two independent points. The laterRUNs in the same stage need no expansion of their own: they are already invalidated by their busted parent layer.
- The lint stage uses the
-
Every repo should have a Gitea Actions workflow (
.gitea/workflows/) that runsscript/cibuild(which runsdocker build --build-arg CHECK_EPOCH="$epoch" .) on push. The Dockerfile runsmake check, so a successful build implies all checks pass — but that implication holds only because of theCHECK_EPOCHcache-bust described above. Without it, an unchanged tree serves the check layer from cache and the build reports a green it never earned. A baredocker build .fails closed by design, on the[ -n "$CHECK_EPOCH" ]guard; always go throughscript/cibuildorscript/docker. Never accept ascript/cibuildpass as evidence without confirming it ran: a sub-second wall time, orCACHEDon the check layer, means nothing was executed. -
Use platform-standard formatters:
blackfor Python,prettierfor JS/CSS/Markdown/HTML,go fmtfor Go. Always use default configuration with two exceptions: four-space indents (except Go), andproseWrap: alwaysfor Markdown (hard-wrap at 80 columns). Documentation and writing repos (Markdown, HTML, CSS) should also have.prettierrcand.prettierignore. -
Pre-commit hook: runs
script/precommit, which callsscript/check. If local testing is not possible in the repo,script/precommitmay skipscript/testand run onlyscript/lintandscript/fmt-check. The hook is installed byscript/install-precommit; the Makefile must provide amake hookstarget that shims to it. -
All repos with software must have tests that run via the platform-standard test framework (
go test,pytest,jest/vitest, etc.). If no meaningful tests exist yet, add the most minimal test possible — e.g. importing the module under test to verify it compiles/parses. There is no excuse formake testto be a no-op. -
make testmust complete in under 20 seconds. Add a 30-second timeout in the Makefile. -
make testshould use the conditional verbose rerun pattern. Run tests without-v(verbose) first. If tests fail, automatically rerun with-vto show full output. This keeps CI logs anddocker buildoutput clean on success (just package/suite summaries) while providing full diagnostic detail on failure (every test case, every assertion). The general shell pattern:test: @<test-command> || \ { echo "--- Rerunning with -v for details ---"; \ <test-command-with-v>; exit 1; }Go example:
test: @go test -timeout 30s -race -cover ./... || \ { echo "--- Rerunning with -v for details ---"; \ go test -timeout 30s -race -v ./...; exit 1; }Python example:
test: @python -m pytest || \ { echo "--- Rerunning with -v for details ---"; \ python -m pytest -v; exit 1; }The
exit 1ensures the target always fails after a rerun — the first run already proved the tests are broken, so the build must not pass even if a flaky test happens to succeed on the second attempt. The rerun exists solely for diagnostic output. -
Docker builds must complete in under 5 minutes.
-
make checkmust not modify any files in the repo. Tests may use temporary directories. -
mainmust always passmake check, no exceptions. -
Never commit secrets.
.envfiles, credentials, API keys, and private keys must be in.gitignore. No exceptions. -
.gitignoreshould be comprehensive from the start: OS files (.DS_Store), editor files (.swp,*~), language build artifacts, andnode_modules/. Fetch the standard.gitignorefromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignorewhen setting up a new repo. -
No build artifacts in version control. Code-derived data (compiled bundles, minified output, generated assets) must never be committed to the repository if it can be avoided. 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 withgo get, which downloads code but does not execute code generation. -
Never use
git add -Aorgit add .. Always stage files explicitly by name. -
Never force-push to
main. -
Make all changes on a feature branch. You can do whatever you want on a feature branch.
-
.golangci.ymlis standardized and must NEVER be modified by an agent, only manually by the user. Fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml. The canonical golangci-lint version is v2.12.2 (released 2026-05-06), installed commit-pinned viago install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9cf3faa61a4e378e879ece580256d76e5. -
script/bootstrapin Go repos must install the pinned golangci-lint whenever the installed version does not match the pin — not merely when the binary is absent — and must then verify the install took effect by re-resolving the binary throughPATH. The presence testif missing golangci-lint; then go install "$GOLANGCI_LINT_REF"; fiis wrong: it testsPATHpresence and never version, so on any already-provisioned machine the pin is inert and a version bump is a no-op. Meanwhile the Dockerfile installs unconditionally into a clean image, so CI and local silently disagree about what the linter even is. Observed consequences: a localmake checkgreen whilemake dockerrejected the same commit with sixgoconstfindings, and a container linter surfacing thirteen findings the host run missed. A stale host linter does not merely fail to prove the tree is clean — it hides findings only the container can see. This is a deliberate departure from the node handling described above, which uses whatever node is installed: the linter version is the specific thing being held equal between host and container, so for it, presence is not enough.Comparing versions is necessary but not sufficient, because the obvious fix also fails green.
go installwrites toGOBIN(orGOPATH/bin) while callers resolvegolangci-lintthroughPATH. If a different binary shadows it earlier inPATH, the install genuinely succeeds and changes nothing any caller will ever see: bootstrap prints success and the nextmake lintstill runs the stale linter. That is worse than no fix, because it converts a known-stale toolchain into one everyone believes is pinned. The canonical form, placed inscript/bootstrapafter Go itself is present:# golangci-lint v2.12.2, 2026-05-06 GOLANGCI_LINT_VERSION="2.12.2" GOLANGCI_LINT_REF="github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9cf3faa61a4e378e879ece580256d76e5" # The version golangci-lint reports, resolved the way callers resolve it. # Prints nothing when the binary is absent, exits non-zero, or prints # something unparseable — all of which must read as "does not match". golangci_lint_version() { command -v golangci-lint >/dev/null 2>&1 || return 0 golangci-lint --version 2>/dev/null | head -n 1 | sed -n 's/.*has version v\{0,1\}\([0-9][0-9.]*\).*/\1/p' } ensure_golangci_lint() { if [ "$(golangci_lint_version)" = "$GOLANGCI_LINT_VERSION" ]; then return 0 fi echo "bootstrap: installing golangci-lint $GOLANGCI_LINT_VERSION" go install "$GOLANGCI_LINT_REF" # go install writes to GOBIN (or GOPATH/bin); callers resolve through # PATH. Re-resolve through PATH and assert the install took effect. hash -r 2>/dev/null || true got="$(golangci_lint_version)" if [ "$got" = "$GOLANGCI_LINT_VERSION" ]; then return 0 fi gobin="$(go env GOBIN)" [ -n "$gobin" ] || gobin="$(go env GOPATH)/bin" found="$(command -v golangci-lint 2>/dev/null || true)" echo "bootstrap: installed golangci-lint $GOLANGCI_LINT_VERSION into" \ "$gobin, but PATH resolves golangci-lint to ${found:-nothing}," \ "reporting version ${got:-unparseable}." >&2 echo "bootstrap: remove that binary or put $gobin earlier in PATH," \ "then re-run bootstrap." >&2 exit 1 }Three properties are load-bearing; each guards a failure mode that otherwise fails green:
- Compare the installed version against the pin, never test presence. This is what makes a version bump propagate to machines that already have some golangci-lint.
- After installing, re-resolve the binary the way callers resolve it —
through
PATH, not the pathgo installwrote to — and assert--versionreports the pin. When it does not, fail non-zero and name the shadowing pathcommand -vactually found, the version it reports, and the directory the install wrote to. That is a condition a human has to fix by hand, so bootstrap must not print success in it. Usehash -rfirst so the shell does not answer from its own lookup cache. - A mis-parse must fall through to reinstall, never to a false match. Absent binary, non-zero exit, empty output, and unrecognised output all yield an empty string, which compares unequal to the pin. The failure direction is always a redundant install, never a skipped one.
Keep it POSIX sh: no bashisms, no arrays, no
[[, nogrep -P.On the hash-pinning rule.
@c0d3ddc9cf3faa61a4e378e879ece580256d76e5is a commit hash, not a server-mutable version tag, and the go command verifies the fetched module against the checksum database andgo.sum— the mechanism the hash-pinning rule at the top of this document already names as acceptable for Go modules. So the ref stays a barego installof a commit-pinned module rather than ago.modtool dependency; the linter is a bootstrap prerequisite rather than part of the module graph, and tracking it as a tool dependency would pull its whole dependency tree into every consuming repo'sgo.modandgo.sum.GOLANGCI_LINT_VERSIONis a separate string because the ref is a hash and carries no readable version; it must be updated with the ref. That commit is thev2.12.2tag commit, so the go command resolves it tov2.12.2and the built binary reports2.12.2. If a pin is ever moved to a commit that carries no release tag, the binary will report a pseudo-version instead andGOLANGCI_LINT_VERSIONmust be set to whatever--versionthen prints.Verifying a change to this logic requires a negative control run in an environment where a shadowing binary exists earlier in
PATHthan the install target. Without that, the control passes against the naive compare-then-install form as well and therefore proves nothing. Also check the mis-parse direction by feeding it unparseable--versionoutput and confirming it reinstalls rather than reporting a match. -
When pinning images or packages by hash, add a comment above the reference with the version and date (YYYY-MM-DD).
-
Use
yarn, notnpm. -
Write all dates as YYYY-MM-DD (ISO 8601).
-
Simple projects should be configured with environment variables.
-
Dockerized web services listen on port 8080 by default, overridable with
PORT. -
HTTP/web services must be hardened for production internet exposure before tagging 1.0. This means full compliance with security best practices including, without limitation, all of the following:
- Security headers on every response:
Strict-Transport-Security(HSTS) withmax-ageof at least one year andincludeSubDomains.Content-Security-Policy(CSP) with a restrictive default policy (default-src 'self'as a baseline, tightened per-resource as needed). Never useunsafe-inlineorunsafe-evalunless unavoidable, and document the reason.X-Frame-Options: DENY(orSAMEORIGINif framing is required). Prefer theframe-ancestorsCSP directive as the primary control.X-Content-Type-Options: nosniff.Referrer-Policy: strict-origin-when-cross-origin(or stricter).Permissions-Policyrestricting access to browser features the application does not use (camera, microphone, geolocation, etc.).
- Request and response limits:
- Maximum request body size enforced on all endpoints (e.g. Go
http.MaxBytesReader). Choose a sane default per-route; never accept unbounded input. - Maximum response body size where applicable (e.g. paginated APIs).
ReadTimeoutandReadHeaderTimeouton thehttp.Serverto defend against slowloris attacks.WriteTimeouton thehttp.Server.IdleTimeouton thehttp.Server.- Per-handler execution time limits via
context.WithTimeoutor chi/stdlibmiddleware.Timeout.
- Maximum request body size enforced on all endpoints (e.g. Go
- Authentication and session security:
- Rate limiting on password-based authentication endpoints. API keys are high-entropy and not susceptible to brute force, so they are exempt.
- CSRF tokens on all state-mutating HTML forms. API endpoints
authenticated via
Authorizationheader (Bearer token, API key) are exempt because the browser does not attach these automatically. - Passwords stored using bcrypt, scrypt, or argon2 — never plain-text, MD5, or SHA.
- Session cookies set with
HttpOnly,Secure, andSameSite=Lax(orStrict) attributes.
- Reverse proxy awareness:
- True client IP detection when behind a reverse proxy
(
X-Forwarded-For,X-Real-IP). The application must accept forwarded headers only from a configured set of trusted proxy addresses — never trustX-Forwarded-Forunconditionally.
- True client IP detection when behind a reverse proxy
(
- CORS:
- Authenticated endpoints must restrict
Access-Control-Allow-Originto an explicit allowlist of known origins. Wildcard (*) is acceptable only for public, unauthenticated read-only APIs.
- Authenticated endpoints must restrict
- Error handling:
- Internal errors must never leak stack traces, SQL queries, file paths,
or other implementation details to the client. Return generic error
messages in production; detailed errors only when
DEBUGis enabled.
- Internal errors must never leak stack traces, SQL queries, file paths,
or other implementation details to the client. Return generic error
messages in production; detailed errors only when
- TLS:
- Services never terminate TLS directly. They are always deployed behind
a TLS-terminating reverse proxy. The service itself listens on plain
HTTP. However, HSTS headers and
Securecookie flags must still be set by the application so that the browser enforces HTTPS end-to-end.
- Services never terminate TLS directly. They are always deployed behind
a TLS-terminating reverse proxy. The service itself listens on plain
HTTP. However, HSTS headers and
This list is non-exhaustive. Apply defense-in-depth: if a standard security hardening measure exists for HTTP services and is not listed here, it is still expected. When in doubt, harden.
- Security headers on every response:
-
README.mdis the primary documentation. Required sections:- Description: First line must include the project name, purpose, category (web server, SPA, CLI tool, etc.), license, and 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.
- Entrypoints: Opens by stating that the repo adheres to the
Scripts to Rule Them All
standard (with that link), then documents each provided
script/entrypoint and its purpose. - Rationale: Why does this exist?
- Design: How is the program structured?
- TODO: Update meticulously, even between commits. When planning, put the todo list in the README so a new agent can pick up where the last one left off.
- License: MIT, GPL, or WTFPL. Ask the user for new projects. Include a
LICENSEfile in the repo root and a License section in the README. - Author: @sneak.
-
First commit of a new repo should contain only
README.md. -
Go module root:
sneak.berlin/go/<name>. Always rungo mod tidybefore committing. -
Use SemVer.
-
Database migrations live in
internal/db/migrations/and must be embedded in the binary.000_migration.sql— contains ONLY the creation of the migrations tracking table itself. Nothing else.001_schema.sql— the full application schema.- Pre-1.0.0: never add additional migration files (002, 003, etc.).
There is no installed base to migrate. Edit
001_schema.sqldirectly. - Post-1.0.0: add new numbered migration files for each schema change. Never edit existing migrations after release.
-
All repos should have an
.editorconfigenforcing the project's indentation settings. -
Avoid putting files in the repo root unless necessary. Root should contain only project-level config files (
README.md,Makefile,Dockerfile,LICENSE,.gitignore,.editorconfig,REPO_POLICIES.md, and language-specific config). Everything else goes in a subdirectory. Canonical subdirectory names:bin/— executable scripts and toolscmd/— Go command entrypointsconfigs/— configuration templates and examplesdeploy/— deployment manifests (k8s, compose, terraform)docs/— documentation and markdown (README.md stays in root)internal/— Go internal packagesinternal/db/migrations/— database migrationspkg/— Go library packagesshare/— systemd units, data filesstatic/— static assets (images, fonts, etc.)web/— web frontend source
-
When setting up a new repo, files from the
promptsrepo may be used as templates. Fetch them fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/<path>. -
New repos must contain at minimum:
README.md,.git,.gitignore,.editorconfigLICENSE,REPO_POLICIES.md(copy from thepromptsrepo)Makefilescript/entrypoints (bootstrap,setup,projectname,test,lint,fmt,fmt-check,check,docker,cibuild,precommit,install-precommit)Dockerfile,.dockerignore.gitea/workflows/check.yml- Go:
go.mod,go.sum,.golangci.yml - JS:
package.json,yarn.lock,.prettierrc,.prettierignore - Python:
pyproject.toml