Files
prompts/prompts/REPO_POLICIES.md
user 3ec7ecfca8
All checks were successful
check / check (push) Successful in 8s
docs: document fail-fast lint stage pattern for Dockerfiles
Adds detailed documentation of the multistage Docker build pattern
where a separate lint stage runs fmt-check and lint before the build
stage begins. Includes the standard Dockerfile template, the BuildKit
dependency trick (COPY --from=lint), go:embed placeholder handling,
and CGO/system library notes.
2026-03-12 16:44:47 -07:00

16 KiB

title, last_modified
title last_modified
Repository Policies 2026-03-12

This document covers repository structure, tooling, and workflow standards. Code style conventions are in separate documents:


  • Cross-project documentation (such as this file) must include last_modified: YYYY-MM-DD in 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 in go.sum, npm integrity hash in lockfile, GitHub Actions @<commit-sha>). No exceptions. This also means never curl | bash to 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 Makefile with these targets: make test, make lint, make fmt (writes), make fmt-check (read-only), make check (prereqs: test, lint, fmt-check), make docker, and make hooks (installs pre-commit hook). A model Makefile is at https://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile.

  • 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 types make<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 run make check as a build step so the build fails if the branch is not green. For non-server repos, the Dockerfile should bring up a development environment and run make check. For server repos, make check should run as an early build stage before the final image is assembled.

  • 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-lint image (pinned by hash). This stage runs make fmt-check and make lint before the full build begins. The build stage then declares an explicit dependency on the lint stage via COPY --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 . .
    RUN 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 . .
    RUN 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-lint image 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/null is 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.
    • If the project uses //go:embed directives 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 with apk add.
    • The build stage runs make test after compilation setup. Tests run in the build stage, not the lint stage, because they may require compiled artifacts or heavier dependencies.
  • Every repo should have a Gitea Actions workflow (.gitea/workflows/) that runs docker build . on push. Since the Dockerfile already runs make check, a successful build implies all checks pass.

  • Use platform-standard formatters: black for Python, prettier for JS/CSS/Markdown/HTML, go fmt for Go. Always use default configuration with two exceptions: four-space indents (except Go), and proseWrap: always for Markdown (hard-wrap at 80 columns). Documentation and writing repos (Markdown, HTML, CSS) should also have .prettierrc and .prettierignore.

  • Pre-commit hook: make check if local testing is possible, otherwise make lint && make fmt-check. The Makefile should provide a make hooks target to install the pre-commit hook.

  • 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 for make test to be a no-op.

  • make test must complete in under 20 seconds. Add a 30-second timeout in the Makefile.

  • Docker builds must complete in under 5 minutes.

  • make check must not modify any files in the repo. Tests may use temporary directories.

  • main must always pass make check, no exceptions.

  • Never commit secrets. .env files, credentials, API keys, and private keys must be in .gitignore. No exceptions.

  • .gitignore should be comprehensive from the start: OS files (.DS_Store), editor files (.swp, *~), language build artifacts, and node_modules/. Fetch the standard .gitignore from https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore when 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 with go get, which downloads code but does not execute code generation.

  • Never use git add -A or git 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.yml is standardized and must NEVER be modified by an agent, only manually by the user. Fetch from https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml.

  • When pinning images or packages by hash, add a comment above the reference with the version and date (YYYY-MM-DD).

  • Use yarn, not npm.

  • 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) with max-age of at least one year and includeSubDomains.
      • Content-Security-Policy (CSP) with a restrictive default policy (default-src 'self' as a baseline, tightened per-resource as needed). Never use unsafe-inline or unsafe-eval unless unavoidable, and document the reason.
      • X-Frame-Options: DENY (or SAMEORIGIN if framing is required). Prefer the frame-ancestors CSP directive as the primary control.
      • X-Content-Type-Options: nosniff.
      • Referrer-Policy: strict-origin-when-cross-origin (or stricter).
      • Permissions-Policy restricting 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).
      • ReadTimeout and ReadHeaderTimeout on the http.Server to defend against slowloris attacks.
      • WriteTimeout on the http.Server.
      • IdleTimeout on the http.Server.
      • Per-handler execution time limits via context.WithTimeout or chi/stdlib middleware.Timeout.
    • 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 Authorization header (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, and SameSite=Lax (or Strict) 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 trust X-Forwarded-For unconditionally.
    • CORS:
      • Authenticated endpoints must restrict Access-Control-Allow-Origin to an explicit allowlist of known origins. Wildcard (*) is acceptable only for public, unauthenticated read-only APIs.
    • 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 DEBUG is enabled.
    • 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 Secure cookie flags must still be set by the application so that the browser enforces HTTPS end-to-end.

    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.

  • README.md is 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.
    • 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 LICENSE file 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 run go mod tidy before 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.sql directly.
    • Post-1.0.0: add new numbered migration files for each schema change. Never edit existing migrations after release.
  • All repos should have an .editorconfig enforcing 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 tools
    • cmd/ — Go command entrypoints
    • configs/ — configuration templates and examples
    • deploy/ — deployment manifests (k8s, compose, terraform)
    • docs/ — documentation and markdown (README.md stays in root)
    • internal/ — Go internal packages
    • internal/db/migrations/ — database migrations
    • pkg/ — Go library packages
    • share/ — systemd units, data files
    • static/ — static assets (images, fonts, etc.)
    • web/ — web frontend source
  • When setting up a new repo, files from the prompts repo may be used as templates. Fetch them from https://git.eeqj.de/sneak/prompts/raw/branch/main/<path>.

  • New repos must contain at minimum:

    • README.md, .git, .gitignore, .editorconfig
    • LICENSE, REPO_POLICIES.md (copy from the prompts repo)
    • Makefile
    • Dockerfile, .dockerignore
    • .gitea/workflows/check.yml
    • Go: go.mod, go.sum, .golangci.yml
    • JS: package.json, yarn.lock, .prettierrc, .prettierignore
    • Python: pyproject.toml