From 82ccb1fc1bdad3f2dabe160e0ef82d93f22dcf34 Mon Sep 17 00:00:00 2001 From: CTO Date: Mon, 22 Jun 2026 15:12:04 +0200 Subject: [PATCH] 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 --- .gitignore | 9 +- CONTRIBUTING.md | 23 +-- LICENSE | 202 ++++++++++++++++++++++++ README.md | 26 ++- docs/adrs/0002-hatch-detail-stack.md | 129 +++++++++++++++ docs/engineering/first-engineer-role.md | 48 ++++-- docs/engineering/hatch-architecture.md | 144 +++++++++++++++++ docs/engineering/local-dev.md | 147 +++++++++++++++++ docs/engineering/onboarding.md | 13 +- docs/engineering/tech-stack.md | 68 +++++--- 10 files changed, 744 insertions(+), 65 deletions(-) create mode 100644 LICENSE create mode 100644 docs/adrs/0002-hatch-detail-stack.md create mode 100644 docs/engineering/hatch-architecture.md create mode 100644 docs/engineering/local-dev.md diff --git a/.gitignore b/.gitignore index 10976539..425aea20 100644 --- a/.gitignore +++ b/.gitignore @@ -8,9 +8,16 @@ *.out go.work -# Build +# Build artifacts /bin /dist +/hatch +/hatch-* +bin/ +*.db +*.db-journal +*.db-wal +*.db-shm # IDE .idea/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 676702b9..c2056e1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,8 +9,9 @@ owner-identifier/short-description ``` Examples: -- `cto/ELF-4-engineering-foundation` -- `eng-1/add-auth-middleware` +- `cto/ELF-15-hatch-stack-migration` +- `engineer/hatch-bootstrap` +- `engineer/hatch-storage` ## No Direct Commits to `main` @@ -36,7 +37,7 @@ Good: Add rotating refresh tokens Using a rotating refresh token strategy prevents replay attacks -and gives us a clean theft-detection signal. See ADR-003. +and gives us a clean theft-detection signal. See ADR-0003. Co-Authored-By: Paperclip ``` @@ -48,20 +49,20 @@ Update auth.ts ## Code Style -- TypeScript strict. No `any` unless explicitly approved — use `unknown` + narrowing. -- Keep `lib/` (pure logic) and `services/` (I/O, DB, network) separate. -- Prefer small modules over clever abstractions. -- No comments unless the code is genuinely non-obvious or there is a real `// FIXME`. -- No defensive try/catch around things that should not fail. Let it throw. -- Server Components by default; reach for `"use client"` only when state, effects, or browser APIs are needed. +- **Go, idiomatic.** `gofmt` clean, `go vet ./...` clean. Prefer the standard library over new dependencies. Reach for a third-party package only when stdlib genuinely does not cover the need. +- **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. +- **Server-rendered by default.** Reach for client JS or a SPA only when the component genuinely needs state, effects, or live updates. The v0.1 web UI is HTML templates plus a small vanilla-JS SSE client. +- **Keep packages small.** Prefer small, focused packages over clever abstractions. One file per route group in `internal/handler/`. +- **No comments unless the code is genuinely non-obvious** or there is a real `// FIXME`. Let the code explain itself; let the commit message explain the *why*. +- **No defensive error handling around things that should not fail.** Let it panic or return the error. Wrap at the boundary, not at every call site. ## Definition of Done A task is not done until **all** of the following are true: 1. Code is written and reviewed. -2. Tests pass. CI is green. -3. Documentation is updated. +2. Tests pass. `go test ./...` is green. CI is green. +3. Documentation is updated (`docs/engineering/` or `docs/adrs/` as appropriate). 4. No secrets in plain text. 5. User-facing changes are validated. 6. Rollback path is known. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f0d64fda --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of tracking, improving, or otherwise + addressing the Work, but excludes communication that is conspicuously + marked or otherwise designated in writing by the copyright owner as + "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for describing the origin of the Work and + reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Support. While redistributing the Work or + Derivative Works thereof, You may accept and charge a fee for, + acceptance of support, warranty, indemnity, or other liability + obligations and/or rights consistent with this License. However, + in accepting such obligations, You may act only on Your own + behalf and on Your sole responsibility, not on behalf of any other + Contributor, and only if You agree to indemnify, defend, and hold + each Contributor harmless for any liability incurred by, or claims + asserted against, such Contributor by reason of your accepting any + such warranty or support. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file name and extension be included on the same line as the + copyright notice for ease of identification within third-party + archives. + + Copyright 2026 El Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index b91e77d7..ada00b9a 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,23 @@ El Foundation builds institutions that outlast their founders. We create technol 1. Read the [company charter](docs/company/charter.md) to understand why we exist. 2. Read the [operating model](docs/company/operating-model.md) to understand how decisions are made. 3. Read [CONTRIBUTING.md](CONTRIBUTING.md) before making any changes. +4. Read [docs/engineering/local-dev.md](docs/engineering/local-dev.md) for the day-to-day workflow. +5. Read [docs/engineering/hatch-architecture.md](docs/engineering/hatch-architecture.md) for the component map. ## Repository Layout ``` ├── .github/workflows/ # CI/CD definitions +├── cmd/hatch/ # Server entrypoint (Go binary) ├── docs/ │ ├── company/ # Founding documents (charter, org, etc.) -│ ├── engineering/ # Engineering standards and decisions +│ ├── engineering/ # Engineering standards, architecture, local dev │ └── adrs/ # Architecture Decision Records -├── apps/ # Application code (TBD — created when product work begins) -├── packages/ # Shared libraries and packages -└── scripts/ # Automation and utility scripts +├── internal/ # Go packages (handler, store, ...) +├── Dockerfile # Multi-stage static binary build (golang → scratch) +├── docker-compose.yml # Local stack with optional Caddy sidecar +├── Caddyfile # TLS reverse proxy for the demo host +└── go.mod # Go module definition ``` ## Hatch — Deploy in one command @@ -34,7 +39,14 @@ Hatch is a self-hostable HTTP request inspector + mocker. Ship it to any VPS wit ```bash docker compose up --build # Hatch UI: http://localhost:8080 -# Capture endpoint: http://localhost:8080/{endpoint-id} +# Health check: http://localhost:8080/healthz +``` + +Or run the binary directly: + +```bash +go run ./cmd/hatch +# Health check: http://localhost:8080/healthz ``` ### Production (with HTTPS via Caddy) @@ -64,7 +76,7 @@ Caddy terminates TLS and reverse-proxies to the Hatch Go binary. The Hatch conta ## Technology Stack -See [docs/engineering/tech-stack.md](docs/engineering/tech-stack.md) for current choices and rationale. +See [docs/engineering/tech-stack.md](docs/engineering/tech-stack.md) for current choices and rationale, and [docs/adrs/](docs/adrs/) for the decision records that produced them. ## Contributing @@ -72,4 +84,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). ## License -Proprietary — All rights reserved. +Apache-2.0 — see [LICENSE](LICENSE). diff --git a/docs/adrs/0002-hatch-detail-stack.md b/docs/adrs/0002-hatch-detail-stack.md new file mode 100644 index 00000000..43a6f1b7 --- /dev/null +++ b/docs/adrs/0002-hatch-detail-stack.md @@ -0,0 +1,129 @@ +# 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 | Apache-2.0 | Permissive, explicit patent grant, matches Go ecosystem norms. | +| 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: Apache-2.0 + +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). +- 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. | +| Apache-2.0 | Relicense with contributor agreement. Not a code change, but a legal process. Not reversible by us alone. | +| 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 diff --git a/docs/engineering/first-engineer-role.md b/docs/engineering/first-engineer-role.md index e17aa817..a463a52b 100644 --- a/docs/engineering/first-engineer-role.md +++ b/docs/engineering/first-engineer-role.md @@ -4,6 +4,8 @@ El Foundation has established its charter, operating model, and engineering baseline. We are pre-product but have a clear technical direction. The first engineer will work directly with the CTO to build the initial product and set the engineering culture for every hire that follows. +The engineering stack is **Go + SQLite + stdlib `net/http` + server-rendered HTML + SSE**, per [ADR-0001](https://github.com/elfoundation/hatch/blob/main/docs/adrs/0001-stack.md) and [ADR-0002](adrs/0002-hatch-detail-stack.md). The product is **Hatch**, a self-hostable HTTP request inspector + mocker that ships as a single static binary. + ## What They Will Own - **Product implementation.** Write the first lines of production code. Own features end-to-end from task assignment to merge. @@ -16,37 +18,47 @@ El Foundation has established its charter, operating model, and engineering base ### Must-Have -- **TypeScript** — strong typed-language fundamentals, comfortable with strict mode -- **React / Next.js** — experience with App Router, Server Components, and modern React patterns -- **Relational databases** — schema design, query optimization, migration discipline (PostgreSQL preferred) -- **Git and GitHub** — branching, rebasing, PR discipline, code review -- **Testing mindset** — writes tests as a default, not an afterthought +- **Go** — comfortable with the language, idiomatic style, the `net/http` package, and the standard library. A `go test ./...` / `go vet ./...` workflow is the daily loop. +- **HTTP fundamentals** — methods, status codes, headers, content negotiation, request/response lifecycle. SSE and chunked transfer are a plus. +- **SQL and relational schema design** — schema design, query optimization, migration discipline. SQLite-specific knowledge is a plus but not required. +- **Git and GitHub** — branching, rebasing, PR discipline, code review. +- **Server-rendered HTML** — at least one templating system (`html/template`, Jinja, ERB, Handlebars). Comfort with progressive enhancement. +- **Testing mindset** — writes tests as a default, not an afterthought. Knows when to reach for `httptest`, table tests, and in-memory fixtures. ### Nice-to-Have -- **Prisma ORM** — or similar type-safe ORM experience -- **Tailwind CSS** — or strong utility-first CSS experience -- **Monorepo tooling** — Turborepo, Nx, or similar -- **Cloud infrastructure** — Vercel, AWS, Fly.io, or similar -- **Authentication / security** — OAuth, JWT, session management -- **Mongolian language or market context** — our early users are likely in Mongolia +- **`net/http` internals** — middleware, `http.Handler` composition, the `ServeMux` method-based routing added in Go 1.22. +- **Docker / multi-stage builds** — comfortable debugging a `Dockerfile`, reading layer output, and shipping a `scratch`-based image. +- **SQLite** — pragmas, indexes, JSON columns, the `modernc.org/sqlite` driver. +- **Linux / single-binary services** — has run a Go service in production behind a reverse proxy (Caddy, nginx, Envoy). +- **Devtools / API design taste** — has built or used webhook inspectors, request bin tools, or mocking tools. Empathy for the user is the job. +- **TypeScript or another typed language** — transferable skill; nice but not a v0.1 requirement. +- **Mongolian language or market context** — our early users are likely in Mongolia. + +### Explicitly Deferred (not required for v0.1) + +- Frontend frameworks (React, Next.js, Vue) — Hatch v0.1 is server-rendered. We may adopt one later; we are not starting there. +- TypeScript / Node.js — the foundation is Go. TypeScript remains in the toolbox for tooling only. +- Monorepo tooling (Turborepo, Nx) — single Go module, single binary, no need. +- Tailwind / utility CSS — hand-written CSS is enough for v0.1 surfaces. ## Attributes We Value -- **Slope over intercept.** We care more about how fast you learn than what you already know. +- **Slope over intercept.** We care more about how fast you learn than what you already know. Go fluency is learnable; engineering judgment is not. - **Writes things down.** Async-first communication. Clear documentation. Decision records. - **Disagrees and commits.** Healthy dissent, then full commitment once a decision is made. - **Protects focus.** Ruthless prioritization. Says no to multitasking. - **Pulls for bad news.** Surfaces problems early. Does not hide blockers. +- **Boring by default.** Reaches for the standard library before adding a dependency. ## First 30-Day Priorities | Week | Focus | Deliverable | |---|---|---| -| 1 | Onboard and ship a small fix | Merged PR, environment verified | -| 2 | Build first feature slice | Working code in staging | -| 3 | Establish testing pattern | Test coverage for new code, CI green | -| 4 | Document and refine | Updated docs, onboarding feedback, first ADR contribution | +| 1 | Onboard and ship the bootstrap | Merged `engineer/hatch-bootstrap` PR with `cmd/hatch` HTTP server, `Dockerfile`, CI green | +| 2 | Ship Task 2 (SQLite storage layer) | Merged PR with `internal/storage/...`, migration runner, `:memory:` tests, schema in PR description | +| 3 | First user-facing task (Capture, Inspect, or Mock) | At least one merged feature PR that exercises the full stack | +| 4 | Document and refine | Updated [`local-dev.md`](local-dev.md) / [`hatch-architecture.md`](hatch-architecture.md) with real friction, first own-authored ADR | ## Reporting @@ -63,7 +75,9 @@ El Foundation has established its charter, operating model, and engineering base ## Recommendation -**Hire a mid-level full-stack engineer with strong TypeScript and Next.js experience.** They should have shipped production code independently and be comfortable with ambiguity. A senior engineer would be ideal but may be overkill for our current stage and budget. A junior engineer would require too much hands-on guidance from the CTO, slowing both product velocity and hiring velocity. +**Hire a mid-level engineer with Go and HTTP-services experience.** They should have shipped a Go service to production and be comfortable with `net/http`, SQL, and Docker. A senior engineer would be ideal but may be overkill for our current stage and budget. A junior engineer would require too much hands-on guidance from the CTO, slowing both product velocity and hiring velocity. + +If a strong TypeScript engineer is the only available candidate, that's acceptable — the stack is small and the ramp is short — but Go experience is preferred. **Suggested title:** Software Engineer **Suggested level:** Mid-level (2–5 years shipping production code) diff --git a/docs/engineering/hatch-architecture.md b/docs/engineering/hatch-architecture.md new file mode 100644 index 00000000..9dea4804 --- /dev/null +++ b/docs/engineering/hatch-architecture.md @@ -0,0 +1,144 @@ +# Hatch Architecture + +Living document. The CTO sketches the component map; the engineer owns the details as the code lands. + +## One-sentence summary + +Hatch is a single Go binary that serves a server-rendered HTML UI and captures, inspects, and mocks HTTP requests against per-endpoint URLs, persisting everything in a single SQLite file. + +## High-level component map + +``` + ┌─────────────────────────────────────┐ + │ Browser (Hatch UI) │ + │ GET /e/{id} + EventSource /events│ + └──────────────┬──────────────────────┘ + │ HTTP / SSE + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ hatch (Go binary) │ +│ │ +│ ┌──────────────┐ ┌────────────────┐ ┌───────────────┐ │ +│ │ http.ServeMux│──▶│ handler layer │──▶│ store layer │ │ +│ │ (stdlib) │ │ (internal/ │ │ (internal/ │ │ +│ │ │ │ handler/) │ │ store/) │ │ +│ └──────────────┘ └────────────────┘ └───────┬───────┘ │ +│ │ │ │ +│ │ ┌────────────────┐ │ │ +│ └────────────▶│ SSE hub │◀─────────┘ │ +│ │ (broadcast │ │ +│ │ new requests)│ │ +│ └────────┬───────┘ │ +│ │ │ +│ ┌────────▼───────┐ │ +│ │ html/template │ │ +│ │ (server-render)│ │ +│ └────────────────┘ │ +└──────────────────────────────┬───────────────────────────────┘ + │ modernc.org/sqlite (pure Go) + ▼ + ┌──────────────┐ + │ hatch.db │ + │ (SQLite) │ + └──────────────┘ +``` + +## Layer responsibilities + +### `cmd/hatch/main.go` — process entrypoint + +- Reads configuration (env: `PORT`). +- Wires the `http.ServeMux` to the handler layer. +- Starts `http.ListenAndServe`. Logs to stdout. Crashes on bind failure. + +### `internal/handler/` — HTTP layer + +- One file per route group: `capture.go`, `inspect.go`, `mock.go`, `sse.go`, `health.go`. +- Handlers take a `store.Repository` (interface) — never a concrete type — so tests can swap in `:memory:` SQLite or a fake. +- Handlers translate HTTP ↔ store calls. They do not contain business logic. +- Server-rendered HTML lives next to the handler that renders it. Templates are `//go:embed`-ed at build time. + +### `internal/store/` — persistence layer + +- `schema.sql` is the canonical DDL. Applied idempotently on first start by `migrate.go`. +- `models.go` defines the Go structs (`Endpoint`, `Request`). +- `repository.go` defines the `Repository` interface. The HTTP layer depends on this interface, not on the SQLite implementation. +- `sqlite_repo.go` is the concrete implementation. Uses `modernc.org/sqlite` (pure Go, no CGO). +- `db.go` opens the database file (or `:memory:` for tests), configures pragmas, returns a `*sql.DB`. + +### SSE hub (in `internal/handler/sse.go`) + +- A goroutine per connected browser, holding an `http.Flusher`. +- A `chan store.Request` that capture handlers publish to. +- The hub fans out new requests to all subscribers for the relevant endpoint. +- No external broker. No Redis. No pubsub library. A `chan` and a `map[endpointID][]chan` is the entire implementation for v0.1. + +## Request lifecycle + +1. **Capture** — a webhook hits `/{endpoint-id}` (any method). The capture handler: + - Reads method, path, query, headers, body. + - Calls `store.AppendRequest(ctx, request)`. + - Publishes the new request to the SSE hub. + - Looks up the endpoint's mock response and returns it. +2. **Inspect** — a browser hits `GET /e/{endpoint-id}`. The inspect handler: + - Calls `store.ListRequests(ctx, endpointID, limit=100)`. + - Renders an HTML page with the request list (newest first). + - The page includes a small vanilla-JS `EventSource` client that subscribes to `/e/{endpoint-id}/events`. +3. **Live update** — the SSE handler holds the connection open, flushes each new request as a `data:` frame, and the browser appends it to the list. +4. **Mock** — `PUT /e/{endpoint-id}/mock` accepts `{status, headers, body}` and updates the endpoint. Subsequent captures to that endpoint return the configured response. + +## Data model + +Two tables. That's it for v0.1. + +``` +endpoints + id TEXT PRIMARY KEY -- URL-safe random ID, e.g. "h7c2k" + created_at INTEGER NOT NULL -- Unix epoch seconds, UTC + mock_status INTEGER -- nullable; 200/201/204/etc. + mock_headers TEXT -- JSON object, nullable + mock_body BLOB -- nullable + +requests + id INTEGER PRIMARY KEY AUTOINCREMENT + endpoint_id TEXT NOT NULL REFERENCES endpoints(id) + received_at INTEGER NOT NULL -- Unix epoch seconds, UTC + method TEXT NOT NULL + path TEXT NOT NULL -- request path within the endpoint + query TEXT NOT NULL -- raw query string + headers TEXT NOT NULL -- JSON object + body BLOB -- nullable + remote_addr TEXT -- for debugging + FOREIGN KEY (endpoint_id) REFERENCES endpoints(id) ON DELETE CASCADE +``` + +Index on `requests(endpoint_id, received_at DESC)` for the list page query. + +## Boundaries + +- **No authentication.** v0.1 is single-user, self-hosted. If you can reach the port, you can read and write. See [v0.1-scope.md](../adrs/v0-1-scope.md). +- **No multi-tenancy.** One SQLite file, one process, one operator. Cloud is v0.2+. +- **No external services.** No Redis, no Postgres, no S3. If the binary needs to phone home, the design is wrong. +- **No client-side framework.** The browser gets HTML and a 50-line `events.js`. Anything more is out of scope for v0.1. + +## Performance budget + +v0.1 is sized for a single developer self-hosting on a $5 VPS: + +- **Cold start:** under 100 ms (Go binary + SQLite open). +- **Capture latency:** under 5 ms p99 on the happy path (no auth, no remote calls). +- **SSE fan-out:** one goroutine per connected browser, no message broker. +- **Database size:** comfortable to 100k requests per endpoint. Retention policy is out of scope for v0.1. + +If a real workload breaks these, we measure first, then add complexity. + +## Future seams (do not build now) + +These are deliberate extension points, not planned features. The v0.1 implementation should not block them but also should not build them. + +- **Pluggable storage** — the `Repository` interface is the seam. A Postgres or S3-backed implementation would slot in without handler changes. +- **Pluggable auth** — a middleware in front of the mux. The v0.1 mux is exposed without auth, which is the right v0.1 default. +- **Replay / forwarding** — the `Request` struct has the full request. Replay is "read from store, write back out." Forwarding is the same plus a destination. +- **Multi-tenant cloud** — partitioning by `endpoint_id` is already first-class. A `tenant_id` column is a v0.2 change. + +When the v0.1 design cannot accommodate one of these without a rewrite, we have under-designed. When the v0.1 design has built one of these speculatively, we have over-designed. diff --git a/docs/engineering/local-dev.md b/docs/engineering/local-dev.md new file mode 100644 index 00000000..8ee5e745 --- /dev/null +++ b/docs/engineering/local-dev.md @@ -0,0 +1,147 @@ +# Local Development + +This is the day-to-day workflow for working on Hatch. If something here disagrees with what you actually observe, update this doc — the docs are a living artifact. + +## Prerequisites + +- **Go 1.25 or newer** — `go version` should report 1.25+. Install from or via your package manager. +- **Docker** (optional but recommended) — only required if you want to exercise the `docker compose` flow. +- **`curl`** — for smoke tests against the running server. +- **`sqlite3`** CLI (optional) — for poking at the database file directly. Not required for the normal workflow. + +## Clone and Build + +```bash +git clone https://github.com/elfoundation/hatch.git +cd hatch +go mod download +``` + +## Run the Server + +The fastest path is `go run`: + +```bash +go run ./cmd/hatch +# hatch starting on :8080 +``` + +In another terminal: + +```bash +curl -fsS http://localhost:8080/healthz +# ok +``` + +The server reads its port from the `PORT` environment variable and defaults to `:8080`. + +```bash +PORT=9090 go run ./cmd/hatch +# hatch starting on :9090 +``` + +## Run the Tests + +```bash +go test ./... +``` + +This runs the full test suite, including the smoke test that boots the HTTP server in-process and hits `/healthz`. Add `-v` for verbose output, `-race` for the race detector. + +```bash +go test ./... -v -race +``` + +## Vet and Format + +```bash +go vet ./... +gofmt -l . +``` + +CI runs `go vet ./...`. `gofmt -l .` lists any unformatted files (no output means everything is formatted). Run `gofmt -w .` to fix formatting in place. + +## Build a Static Binary + +```bash +CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/hatch ./cmd/hatch +ls -lh bin/hatch +``` + +The resulting binary is fully static, has no libc dependency, and runs on any Linux x86_64 with the same kernel. + +## Docker Compose + +For the full local stack (Hatch + optional Caddy reverse proxy for TLS): + +```bash +# Plain HTTP — Hatch only +docker compose up --build + +# With Caddy (HTTPS, self-signed in dev) +docker compose --profile with-caddy up --build +``` + +The compose file reads `.env` for `HATCH_HOSTNAME`. Copy `.env.example` to `.env` and adjust: + +```bash +cp .env.example .env +# Edit HATCH_HOSTNAME=localhost (or your real domain) +``` + +## Where Things Live + +| Path | Purpose | +|---|---| +| `cmd/hatch/` | Server entrypoint. `main.go` wires up `http.ServeMux`, `main_test.go` is the smoke test. | +| `internal/handler/` | HTTP handlers (Capture, Inspect, Mock). One file per route group. | +| `internal/store/` | Storage layer. `schema.sql` is the canonical DDL, `sqlite_repo.go` is the SQLite-backed implementation of the `Repository` interface. | +| `docs/engineering/` | Engineering standards and architecture docs. | +| `docs/adrs/` | Architecture Decision Records. | +| `Dockerfile` | Multi-stage build: `golang:1.25-alpine` → `scratch` with the static binary. | +| `docker-compose.yml` | Local stack: Hatch on `:8080`, optional Caddy sidecar for TLS. | +| `.env.example` | Documented environment variables. | +| `hatch.db` (gitignored) | SQLite database file. Created on first start in the working directory. | + +## SQLite Tips + +The database is a single file. By default it lives at `./hatch.db` in the process working directory. + +```bash +# Inspect the schema +sqlite3 hatch.db '.schema' + +# List endpoints +sqlite3 hatch.db 'SELECT * FROM endpoints;' + +# Reset (destructive) +rm hatch.db +# Schema is recreated on next start +``` + +For tests that need a clean database, use the `:memory:` SQLite database — see `internal/store/sqlite_repo_test.go` for the pattern. Do not point tests at the on-disk database file. + +## Common Tasks + +| Task | Command | +|---|---| +| Run the server | `go run ./cmd/hatch` | +| Run all tests | `go test ./...` | +| Run a single test | `go test ./internal/handler -run TestCapture -v` | +| Vet | `go vet ./...` | +| Format | `gofmt -w .` | +| Build a binary | `CGO_ENABLED=0 go build -o bin/hatch ./cmd/hatch` | +| Docker stack | `docker compose up --build` | +| Reset the database | `rm hatch.db` | + +## Troubleshooting + +**`go: command not found`** — Install Go 1.25+ and ensure `$HOME/go/bin` (or wherever `go install` puts binaries) is on your `PATH`. + +**`bind: address already in use`** — Another process is on `:8080`. Either stop it or run with `PORT=9090 go run ./cmd/hatch`. + +**Tests fail with `database is locked`** — A previous test process left a handle. Look for stray `hatch` processes (`ps aux | grep hatch`) and kill them. Tests should use `:memory:` databases and not share the on-disk file. + +**Docker build fails on `go.sum`** — Run `go mod tidy` locally, commit `go.sum`, rebuild. The Dockerfile copies `go.sum` before `go mod download` for reproducible builds. + +**`CGO_ENABLED` warning during `go build`** — The static-binary build sets `CGO_ENABLED=0` explicitly. If you forget, the binary will dynamically link libc and break the "single static binary" promise. diff --git a/docs/engineering/onboarding.md b/docs/engineering/onboarding.md index e95672ab..4235024d 100644 --- a/docs/engineering/onboarding.md +++ b/docs/engineering/onboarding.md @@ -14,15 +14,20 @@ - [ ] Read [how we decide](../company/how-we-decide.md) - [ ] Read [CONTRIBUTING.md](../../CONTRIBUTING.md) - [ ] Read [tech-stack.md](tech-stack.md) +- [ ] Read [local-dev.md](local-dev.md) +- [ ] Read [hatch-architecture.md](hatch-architecture.md) +- [ ] Read [ADR-0002: Hatch detail stack](adrs/0002-hatch-detail-stack.md) - [ ] Introduce yourself in the team channel (async written standup format) ## Day 2–3: Environment +- [ ] Install Go 1.25 (or newer) — see the [Go install instructions](https://go.dev/doc/install) if you do not have it +- [ ] Verify `go version` reports 1.25 or newer - [ ] Clone the repo -- [ ] Install dependencies (`pnpm install` when package.json exists) -- [ ] Run the dev server locally -- [ ] Verify you can run tests -- [ ] Verify you can run lint +- [ ] Run `go mod download` to fetch dependencies +- [ ] Run `go test ./...` — should pass on a fresh clone +- [ ] Run `go run ./cmd/hatch` and `curl http://localhost:8080/healthz` — should return `ok` +- [ ] Run `docker compose up --build` and visit `http://localhost:8080/healthz` — should return `ok` - [ ] Open your first PR (a README typo fix or doc improvement counts) ## Week 1: First Task diff --git a/docs/engineering/tech-stack.md b/docs/engineering/tech-stack.md index efed674a..faac883d 100644 --- a/docs/engineering/tech-stack.md +++ b/docs/engineering/tech-stack.md @@ -2,48 +2,66 @@ ## Overview -These choices were made by the CTO on 2026-06-22, aligned with the company charter and operating principles. 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. +These choices were made by the CTO on 2026-06-22 to align the engineering foundation with [ADR-0001](https://github.com/elfoundation/hatch/blob/main/docs/adrs/0001-stack.md) 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. -## Frontend - -| Layer | Choice | Rationale | -|---|---|---| -| Framework | Next.js (App Router) | Full-stack React with SSR/SSG, API routes, and a large ecosystem. App Router is the stable future path. | -| Language | TypeScript (strict) | Catches entire classes of bugs at build time. Strict mode is non-negotiable. | -| Styling | Tailwind CSS | Utility-first, colocated with components, no separate CSS files to maintain. | -| UI Components | shadcn/ui pattern | Copy-paste components we own and can customize. No opaque UI library dependencies. | +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 | |---|---|---| -| Database | PostgreSQL | Proven, feature-rich, great ORM support. Our relational data model fits it well. | -| ORM | Prisma | Type-safe queries, excellent migration tooling, good DX. | -| Auth | NextAuth.js | Battle-tested, supports many providers, integrates cleanly with Next.js. | -| Storage | Cloudflare R2 | S3-compatible, zero egress fees, good for file uploads and static assets. | -| Payments | TBD | Will evaluate when we have a revenue model. | +| 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 8–10 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 | |---|---|---| -| Package Manager | pnpm | Fast, disk-space efficient, strict `node_modules` layout avoids phantom dependencies. | -| Monorepo | Turborepo (when needed) | Caching and task orchestration. Only adopt when we have >1 app or shared package. | -| CI/CD | GitHub Actions | Native GitHub integration, free for public repos, cheap for private. | -| Lint | ESLint + Prettier | Standard, autofixable, low-friction. | +| 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 vet` → `go test` → `docker 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 -- **Server Components by default.** Reach for `"use client"` only when the component actually needs state, effects, or browser APIs. -- **Pure logic in `lib/`, I/O in `services/`.** Business logic does not import from `next/server` or call `fetch` directly. -- **Store money as integers.** MNT (Mongolian Tugrik) in smallest unit. Format on display. +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. Infrastructure changes should be reproducible. +- **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 | |---|---|---|---| -| Hosting provider (Vercel, Fly, AWS?) | Open | CTO | Need product requirements and traffic estimates | -| Monitoring / alerting stack | Open | CTO | Need hosting decision | -| CDN / edge strategy | Open | CTO | Need hosting decision | +| 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 |