hatch-surf/docs/engineering/tech-stack.md
CTO 82ccb1fc1b docs+license: realign engineering foundation to Go stack per ADR-0001/0002
Migration commit for the ELF-15 atomic PR. Realizes the CTO plan in
ELF-13 revision 1 and ADR-0001 (Go + SQLite + stdlib net/http + SSR +
SSE) and the detail choices in ADR-0002 (chi, modernc.org/sqlite, html/
template, stdlib testing, Apache-2.0).

Docs:
- docs/engineering/tech-stack.md rewritten for Go + SQLite + stdlib
  net/http + SSR + SSE. Frontend is server-rendered HTML + a little
  vanilla JS. Packaging is multi-stage golang -> scratch with an
  optional Caddy sidecar. Boring-technology principle kept.
- docs/engineering/first-engineer-role.md rewritten: Go must-have
  (stdlib + HTTP fundamentals + SQL + GitHub), TypeScript/Next.js/
  Prisma/PostgreSQL moved to nice-to-have or deferred. Tailwind and
  monorepo tooling explicitly deferred.
- docs/engineering/onboarding.md: pnpm install / pnpm dev replaced
  with go test ./... and go run ./cmd/hatch. New step: read local-dev
  and hatch-architecture on day 1.
- docs/engineering/local-dev.md (new): day-to-day commands, where
  things live, SQLite tips, troubleshooting. Living doc, engineer owns.
- docs/engineering/hatch-architecture.md (new): component map (http
  ServeMux -> handler layer -> store layer; in-process SSE hub),
  request lifecycle (Capture / Inspect / Mock / Live update), data
  model (endpoints, requests), performance budget, future seams.
- docs/adrs/0002-hatch-detail-stack.md (new): CTO-authored ADR
  closing the open choices in ADR-0001 with concrete picks, named
  alternatives, and a per-choice rollback path. Router: go-chi/chi.
  SQLite driver: modernc.org/sqlite (pure Go, no CGO). Templates:
  stdlib html/template + //go:embed. Tests: stdlib testing + httptest.
  License: Apache-2.0.

Housekeeping:
- README.md: drop the apps/ and packages/ layout lines (no
  monorepo), add local-dev and hatch-architecture pointers, fix
  Hatch demo to point at /healthz, switch license line from
  'Proprietary' to Apache-2.0 with a LICENSE file pointer.
- CONTRIBUTING.md: code-style section rewritten for Go (gofmt,
  go vet, internal/ for pure logic, server-rendered by default);
  branch example uses engineer/hatch-* matching the actual workflow.
- LICENSE: full Apache-2.0 text, copyright El Foundation 2026.
  Per ADR-0002.
- .gitignore: ignore the pre-built 'hatch' binary, bin/ (per ADR
  0002 binary convention), and SQLite files (*.db, *.db-journal,
  *.db-wal, *.db-shm).

Out of scope (handled by ELF-17 onwards, not this PR):
- Storage layer implementation (Task B, ELF-17)
- Capture, Inspect, Mock (Tasks 3-6)
- E2E smoke test (Task 8)

Foundation + this commit are the atomic PR. CI is green (go vet,
go test ./... -race, docker build all pass locally; same gates the
GitHub Actions workflow enforces).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-22 15:12:04 +02:00

5.2 KiB
Raw Blame History

Technology Stack

Overview

These choices were made by the CTO on 2026-06-22 to align the engineering foundation with ADR-0001 and the Hatch product thesis. They are reversible within a day for local development, but would require migration effort once production data exists. All choices default to boring, well-supported technology over novelty.

The stack is chosen so that a single docker compose up (or one binary on a VPS) is the entire product — no separate database server, no build step, no auth process. This is the v0.1 distribution promise; if a choice makes that promise harder, it has to earn its place.

Backend & Data

Layer Choice Rationale
Language Go 1.25 Single static binary, fast iteration, strong stdlib for HTTP and SQL. "One command on a VPS" requires a self-contained binary.
HTTP server stdlib net/http (Go 1.22+ method-based routing) No router dependency for a v0.1 surface. go-chi/chi is allowed where middleware is genuinely needed (SSE).
Database SQLite via modernc.org/sqlite Pure-Go driver keeps the "no CGO" promise. Survives restarts, zero-ops, single file you can cp to back up.
Migrations Hand-rolled runner that applies internal/storage/schema.sql on first start, idempotently Premature tooling is paid complexity. One SQL file, one CREATE TABLE IF NOT EXISTS, ship it.
Templates stdlib html/template Server-rendered HTML with auto-escaping. No JS build step, no hydration cost, no SPA complexity for what is fundamentally a server-driven UI.
Live updates Server-Sent Events (SSE) on net/http One-way push from server to browser, no WebSocket framing, plays nicely with the html/template render path.

Frontend

Layer Choice Rationale
UI architecture Server-rendered HTML + a little vanilla JS The UI is a small request list with one live update. JS is loaded only where it earns its bandwidth (the SSE client). No bundler.
CSS Hand-written CSS in internal/handler/assets/ Tailwind and friends add a build step we explicitly do not want in v0.1. A small style.css is enough for the surfaces we ship.
Client JS Vanilla JS (no framework, no transpiler) One events.js for the SSE subscription. If we ever need more, we will earn a toolchain.

Packaging & Distribution

Layer Choice Rationale
Container Multi-stage Dockerfile (golang:1.25 → scratch) Produces an 810 MB static binary image. No runtime, no shell, no package manager on the final image.
Local dev docker compose with optional Caddy sidecar docker compose up is the one-command demo. Caddy handles TLS for the demo host and stays out of the way for plain HTTP.
Demo TLS Caddy (auto-issues Let's Encrypt in prod, self-signed in dev) Hands-off TLS, sane defaults, plays well with the static-binary promise.
Config Env vars (read with os.Getenv) Twelve-factor. No YAML/JSON config files for v0.1. Defaults documented in .env.example.

Tooling

Layer Choice Rationale
Build go build (with CGO_ENABLED=0 for the final binary) No Makefile needed for the common path.
Tests stdlib testing + net/http/httptest No assertion library until pain demands one.
Vet / lint go vet ./... in CI Stdlib tooling, no golangci-lint config to maintain.
CI GitHub Actions (.github/workflows/ci.yml) go vetgo testdocker build → smoke-test the running image.
Markdown lint markdownlint-cli2 (non-blocking) Editorial consistency, not a gate.
Module path github.com/elfoundation/hatch Matches the GitHub org. Easy to change before any external consumer depends on it.

Principles

These are stack-agnostic and outlive any choice in the tables above. They are the cultural defaults; the tables are the implementation.

  • Server-rendered by default. Reach for client JS or a SPA only when the component genuinely needs state, effects, or live updates.
  • Pure logic in internal/, I/O in adapters. Business logic does not call http.* or database/sql directly. Storage and HTTP are replaced with interfaces in tests.
  • Store timestamps as UTC. Render in local time only at the edge.
  • Single static binary, single file database. If a dependency breaks that, justify it.
  • Observability before optimization. Measure before fixing. No tuning without metrics.
  • Idempotency. Operations should be safe to retry. Migrations, request capture, and SSE reconnect are all idempotent.
  • Boring technology. A new dependency must earn its place. If stdlib covers it, stdlib wins.

Open Decisions

Decision Status Owner Blocker
Router (stdlib net/http vs. go-chi/chi) Provisional — start with stdlib, adopt chi if SSE middleware pain demands it CTO Live SSE implementation in v0.1 Inspect task
Structured logging library Open — slog (stdlib) likely sufficient CTO First production deploy
Tracing / metrics Open CTO Hosting decision
Hosting target (Fly, single VPS, k8s?) Open CEO Need product requirements and traffic estimates