hatch-surf/docs/adrs/0002-hatch-detail-stack.md
CTO abf70715ad docs(license): switch repo to MIT per CEO decision ELF-141
- Replace Apache-2.0 LICENSE with MIT full text (Copyright (c) 2026 El Foundation)
- README License section: 'Hatch is released under the MIT License' with link to LICENSE
- ADR-0002: mark license decision superseded by CEO (ELF-141), preserve Apache-2.0 reasoning as history, update rollback note (MIT->Apache-2.0 compatible upgrade)

Closes ELF-149

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-23 03:59:53 +02:00

134 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (~23× 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