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>
This commit is contained in:
parent
9a29eec1cb
commit
82ccb1fc1b
9
.gitignore
vendored
9
.gitignore
vendored
@ -8,9 +8,16 @@
|
||||
*.out
|
||||
go.work
|
||||
|
||||
# Build
|
||||
# Build artifacts
|
||||
/bin
|
||||
/dist
|
||||
/hatch
|
||||
/hatch-*
|
||||
bin/
|
||||
*.db
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
|
||||
@ -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 <noreply@paperclip.ing>
|
||||
```
|
||||
@ -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.
|
||||
|
||||
202
LICENSE
Normal file
202
LICENSE
Normal file
@ -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.
|
||||
26
README.md
26
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).
|
||||
|
||||
129
docs/adrs/0002-hatch-detail-stack.md
Normal file
129
docs/adrs/0002-hatch-detail-stack.md
Normal file
@ -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
|
||||
@ -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)
|
||||
|
||||
144
docs/engineering/hatch-architecture.md
Normal file
144
docs/engineering/hatch-architecture.md
Normal file
@ -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.
|
||||
147
docs/engineering/local-dev.md
Normal file
147
docs/engineering/local-dev.md
Normal file
@ -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 <https://go.dev/doc/install> 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.
|
||||
@ -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
|
||||
|
||||
@ -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 |
|
||||
|
||||
Loading…
Reference in New Issue
Block a user