# ADR-0002: Hatch detail stack

- Status: Accepted
- Date: 2026-06-22
- Authors: CTO
- Supersedes: none
- Related: [ADR-0001](https://github.com/elfoundation/hatch/blob/main/docs/adrs/0001-stack.md), [Hatch v0.1 plan (ELF-13)](https://github.com/elfoundation/hatch/issues/13)

## Context

[ADR-0001](https://github.com/elfoundation/hatch/blob/main/docs/adrs/0001-stack.md) fixed the v0.1 stack at the level of "Go + SQLite + stdlib `net/http` + server-rendered HTML + SSE + single Docker image." That ADR deliberately did not pick:

- which HTTP router (stdlib `net/http` with method-based mux vs. a third-party router)
- which SQLite driver
- which templating system (only said "server-rendered HTML")
- which test framework
- which license

This ADR closes those gaps with concrete choices, named alternatives, and the rollback path for each.

## Decision

| Concern | Choice | One-line reason |
|---|---|---|
| HTTP router | `go-chi/chi` (v5) for v0.1 | SSE middleware story is cleaner than hand-rolled on `http.ServeMux`; chi is the lightest router that still gives us real middleware composition. |
| SQLite driver | `modernc.org/sqlite` (pure Go) | Keeps the "no CGO" promise that the static-binary distribution rests on. |
| Templates | stdlib `html/template`, embedded with `//go:embed` | Auto-escaping by default, no template-engine dependency, no runtime parsing of the filesystem. |
| Test framework | stdlib `testing` + `net/http/httptest` | No assertion library unless pain demands one. |
| License | MIT (revised; was Apache-2.0) | Permissive, lowest friction for small single-binary dev tools; CEO decision [ELF-141](https://github.com/elfoundation/hatch/issues/141). |
| Config | Environment variables only (`PORT`, `HATCH_DB_PATH`, `HATCH_LISTEN`) | Twelve-factor. No YAML/JSON config files for v0.1. |
| Logging | stdlib `log` to stdout, structured via `slog` (Go 1.21+) | No third-party logging library until we need log shipping. |
| Process model | Single process, one port, in-process SSE hub | v0.1 is a self-hosted single-binary product. No workers, no sidecars. |

### Router: `go-chi/chi`

Chi v5 is the smallest router that still composes well. It sits on top of `http.Handler`, so it does not break the stdlib-first principle — it just gives us `r.Use(middleware.Logger)`, `r.Route("/e/{id}", func(r chi.Router) { ... })`, and a clean SSE middleware (`middleware.Flush`).

**Why not stdlib `net/http` only?** Go 1.22 added method-based routing and path parameters to `http.ServeMux`. For v0.1's surface (~6 routes) it is genuinely enough. We adopt chi for two reasons: (1) the SSE middleware in `chi/middleware` is one line vs. ~20 lines of hand-rolled flush-on-write logic, and (2) the moment we add a second cross-cutting concern (request ID, auth-ready, rate limit) the hand-rolled composition gets ugly fast.

**Reversibility.** Drop-in: chi is an `http.Handler`. Reverting to stdlib `ServeMux` is a one-file change to `cmd/hatch/main.go`. The handler layer is router-agnostic by construction.

### SQLite driver: `modernc.org/sqlite`

Pure Go. Translates SQLite's C source via `ccgo` at build time, so the produced driver is a normal Go package with no `cgo` import.

**Why not `mattn/go-sqlite3`?** It is the most popular SQLite driver, but it requires CGO. CGO forces us to either (a) ship a glibc-linked binary that breaks on musl-based images, or (b) maintain a separate CGO build pipeline. Both are paid complexity for a feature (a C compiler in CI) we do not otherwise need.

**Why not the stdlib `database/sql` only?** The `database/sql` package is the API; it is not a driver. We still need a driver, and we still need migrations, and we still need a `Repository` interface above it. The choice here is the driver.

### Templates: stdlib `html/template`, `//go:embed`-ed

`html/template` auto-escapes context-sensitively (HTML, JS, URL, CSS contexts). It is in the standard library, has no dependencies, and produces no runtime surprises. We embed templates at build time with `//go:embed templates/*.html` so the binary is self-contained and there is no filesystem layout to manage at deploy time.

**Why not `templ` or `jet`?** Both add a build step (`templ generate`) to the dev loop. The v0.1 surface is small enough that a hand-rolled `{{range .Requests}}` is not a liability. We will revisit if template churn becomes a real cost.

### Test framework: stdlib `testing` + `net/http/httptest`

`testing` is in the standard library, plays well with `go test`, and is what every Go engineer already knows. `httptest` gives us `httptest.NewRecorder` for handler unit tests and `httptest.NewServer` for end-to-end smoke tests without a real network socket.

**Why not `testify` or `gocheck`?** No assertion library has yet earned its place. We will adopt one when the boilerplate of `if got != want { t.Errorf(...) }` becomes a real cost, not before.

### License: MIT (revised 2026-06-23)

**Superseded by CEO decision** — [ELF-141](https://github.com/elfoundation/hatch/issues/141) locked the OSS license at **MIT** ahead of the v0.1 public launch. The original Apache-2.0 reasoning is preserved below for history; the repo now ships under MIT (see [`LICENSE`](../../LICENSE)). MIT→Apache-2.0 remains a compatible, reversible upgrade if a corporate contributor raises patent concerns later.

#### Original Apache-2.0 reasoning (superseded)

Permissive. Explicit patent grant. Matches what the Go ecosystem uses (Kubernetes, Docker, the Go toolchain itself are permissive-licensed). Compatible with corporate adoption without legal-review friction.

**Why not MIT?** MIT is also acceptable, but the patent grant in Apache-2.0 is a real protection for downstream contributors and is a default in our target ecosystem.

## Consequences

### Positive

- The full v0.1 dependency surface is: `go-chi/chi`, `modernc.org/sqlite`, and `github.com/google/uuid` (if we use it for endpoint IDs). Everything else is stdlib. That is a small, reviewable surface.
- The single-binary distribution story holds: `CGO_ENABLED=0 go build` produces a working static binary, and the Docker build ends in `scratch` with no runtime.
- The license, the test framework, and the template engine are all things an incoming Go engineer already knows. Zero ramp cost on tooling.
- Each choice has a one-day-or-less reversal path. The stack is composable from independent parts; nothing in this ADR forces a bigger rewrite to undo.

### Negative / Risks

- Chi is one more dependency to track. If a future Go stdlib release subsumes the middleware story, we have a small migration.
- `modernc.org/sqlite` is slower than the C version on raw throughput benchmarks (~2–3× on simple SELECTs). For v0.1's workload (a self-hosted single-user product) this is invisible. We will measure before optimizing.
- Stdlib `html/template` has no inheritance, no partials-with-arguments, and no "components." If template complexity grows, we will feel it. The mitigation is to keep the page count low (Capture page, Inspect page, Mock config page, error page).
- (Historical, pre-MIT switch:) Apache-2.0 is more verbose than MIT. Trivial cost; mentioned for completeness.

## Alternatives Considered

### Option A: `net/http` only (no chi)

- Pros: zero router dependency; one fewer thing to upgrade; Go 1.22's `ServeMux` is genuinely good.
- Cons: SSE flush middleware is hand-rolled; auth/cors/rate-limit middleware are also hand-rolled; the cost of NOT having middleware composition grows with every new cross-cutting concern.
- Why rejected: SSE is in v0.1 (Inspect live). The first place we will want a second middleware is auth, even if auth itself ships later. Paying for chi now is cheaper than paying for hand-rolled composition later.

### Option B: `mattn/go-sqlite3`

- Pros: most popular SQLite driver, fast, well-known.
- Cons: requires CGO. Breaks the static-binary promise or forces a second build pipeline.
- Why rejected: the cost of CGO exceeds the value of `mattn`'s ecosystem familiarity.

### Option C: `templ` for templates

- Pros: type-safe templates, IDE autocompletion, components.
- Cons: requires a code-generation step (`templ generate`) on every save. The dev loop slows down. The runtime feature set is ~equivalent to `html/template` for our surface.
- Why rejected: the v0.1 template surface is small enough that hand-written `html/template` is not yet a liability. We can adopt `templ` later if template churn becomes a real cost.

### Option D: MIT license

- Pros: shorter, more familiar to individual contributors.
- Cons: no explicit patent grant.
- Why rejected: Apache-2.0's patent grant is a real downstream protection and matches our target ecosystem. The longer license header is a trivial cost.

## Rollback Plan

| Choice | Rollback |
|---|---|
| `go-chi/chi` | Replace `chi.Router` with `http.ServeMux` in `cmd/hatch/main.go`. Handlers and store are unchanged. ~half a day. |
| `modernc.org/sqlite` | Swap to `mattn/go-sqlite3`. Add CGO to the Dockerfile. Update CI to install `gcc`. ~half a day, plus the Dockerfile/CI change. |
| `html/template` | Adopt `templ` (or `jet`). Adds a code-generation step. ~1 day plus regen. |
| stdlib `testing` | Adopt `testify`. Mechanical import swap. ~2 hours. |
| MIT (was Apache-2.0) | Relicense with contributor agreement. Not a code change, but a legal process. MIT→Apache-2.0 is a compatible upgrade if patent concerns arise. |
| Env-only config | Adopt `viper` or `koanf`. ~1 day including flag plumbing. |

The first five rollbacks are independent and can be done in any order. The license change is not ours to make unilaterally.

## Related

- [ADR-0001](https://github.com/elfoundation/hatch/blob/main/docs/adrs/0001-stack.md) — parent decision
- [Hatch v0.1 plan (ELF-13)](https://github.com/elfoundation/hatch/issues/13) — build plan and ordering
- [`docs/engineering/tech-stack.md`](../engineering/tech-stack.md) — current stack summary
- [`docs/engineering/hatch-architecture.md`](../engineering/hatch-architecture.md) — component map
- [`docs/engineering/local-dev.md`](../engineering/local-dev.md) — day-to-day commands
