From 0da1f18d2985ecb66b43fd048fd9330ef2359d98 Mon Sep 17 00:00:00 2001 From: dimiro1 Date: Tue, 2 Jun 2026 22:45:57 +0200 Subject: [PATCH] docs: add architecture decision records --- docs/adr/0000-template.md | 22 +++++++ .../adr/0001-record-architecture-decisions.md | 40 ++++++++++++ ...configuration-via-environment-variables.md | 55 +++++++++++++++++ docs/adr/0003-frontend-with-mithril.md | 46 ++++++++++++++ docs/adr/0004-no-nodejs-vendored-js.md | 47 ++++++++++++++ docs/adr/0005-mise-for-toolchain-and-tasks.md | 47 ++++++++++++++ docs/adr/0006-dependency-injection-with-fx.md | 44 +++++++++++++ .../0007-single-binary-embedded-frontend.md | 41 +++++++++++++ docs/adr/0008-sqlite-as-the-datastore.md | 41 +++++++++++++ docs/adr/0009-lua-as-the-function-language.md | 49 +++++++++++++++ docs/adr/0010-in-house-i18n.md | 54 ++++++++++++++++ docs/adr/0011-testing-strategy.md | 61 +++++++++++++++++++ docs/adr/README.md | 31 ++++++++++ 13 files changed, 578 insertions(+) create mode 100644 docs/adr/0000-template.md create mode 100644 docs/adr/0001-record-architecture-decisions.md create mode 100644 docs/adr/0002-configuration-via-environment-variables.md create mode 100644 docs/adr/0003-frontend-with-mithril.md create mode 100644 docs/adr/0004-no-nodejs-vendored-js.md create mode 100644 docs/adr/0005-mise-for-toolchain-and-tasks.md create mode 100644 docs/adr/0006-dependency-injection-with-fx.md create mode 100644 docs/adr/0007-single-binary-embedded-frontend.md create mode 100644 docs/adr/0008-sqlite-as-the-datastore.md create mode 100644 docs/adr/0009-lua-as-the-function-language.md create mode 100644 docs/adr/0010-in-house-i18n.md create mode 100644 docs/adr/0011-testing-strategy.md create mode 100644 docs/adr/README.md diff --git a/docs/adr/0000-template.md b/docs/adr/0000-template.md new file mode 100644 index 0000000..50d5c8a --- /dev/null +++ b/docs/adr/0000-template.md @@ -0,0 +1,22 @@ +# NNNN. Title + +- Status: Proposed | Accepted | Deprecated | Superseded by ADR-NNNN +- Date: YYYY-MM-DD + +## Context + +What is the problem, force, or constraint that motivates a decision? Describe the +situation as neutrally as possible — facts, requirements, and the options on the +table. A reader should be able to understand the tension without already knowing +the answer. + +## Decision + +The change we are making, stated in the active voice: "We will …". Be specific +enough that someone can act on it. + +## Consequences + +What becomes easier and what becomes harder as a result. Include the trade-offs +we accept, the things we explicitly rule out, and any follow-up work this +implies. diff --git a/docs/adr/0001-record-architecture-decisions.md b/docs/adr/0001-record-architecture-decisions.md new file mode 100644 index 0000000..176051c --- /dev/null +++ b/docs/adr/0001-record-architecture-decisions.md @@ -0,0 +1,40 @@ +# 0001. Record architecture decisions + +- Status: Accepted +- Date: 2026-06-02 + +## Context + +Lunar is a self-hosted FaaS platform with a number of deliberate, non-obvious +architectural choices: configuration through the environment, a build-step-free +Mithril frontend, no Node.js in the toolchain, `mise` for tasks, `uber/fx` for +wiring, a single embedded binary, and pure-Go SQLite. These decisions are easy +to misread as accidents when only the resulting code is visible. New +contributors (and our future selves) repeatedly ask "why is it done this way?", +and without a record the answer lives only in memory and scattered commit +messages. + +We want a durable, low-ceremony way to capture the *reasoning* behind decisions +that shape the codebase, separate from the code that implements them and from the +user-facing README. + +## Decision + +We will keep Architecture Decision Records as Markdown files under `docs/adr/`, +one decision per numbered file, following the lightweight format in +[`0000-template.md`](0000-template.md) (Status, Context, Decision, +Consequences). + +ADRs are append-only. Once a record is `Accepted` we do not edit its substance; +a decision that changes is recorded as a new ADR that supersedes the old one, +and the old one's status is updated to point at its replacement. + +## Consequences + +- The rationale behind a choice travels with the repository and is versioned + alongside the code, reviewable in the same pull requests. +- There is a small, well-understood cost to writing a record when a decision is + made. We accept this as cheaper than re-litigating decisions later. +- Reviewers gain a natural place to push back on direction before it is encoded + in the codebase. +- The README stays focused on *using* Lunar; the ADRs explain *building* it. diff --git a/docs/adr/0002-configuration-via-environment-variables.md b/docs/adr/0002-configuration-via-environment-variables.md new file mode 100644 index 0000000..327bc8a --- /dev/null +++ b/docs/adr/0002-configuration-via-environment-variables.md @@ -0,0 +1,55 @@ +# 0002. Configuration via environment variables + +- Status: Accepted +- Date: 2026-06-02 + +## Context + +Lunar ships as a single self-hosted binary that people run on their own +machines, in Docker, and on platforms like Railway. These environments differ in +how they inject settings, but every one of them can set environment variables. +We need a configuration mechanism that: + +- works identically across local, Docker, and PaaS deployments; +- requires no config file to exist for a first run (good defaults); +- keeps secrets such as the API key out of the source tree; +- is straightforward to test without mutating global process state. + +The realistic alternatives were a config file format (YAML/TOML), command-line +flags, or environment variables. Config files add a parsing layer and a "where +does it live" question for a single-binary tool. Flags are awkward to thread +through container platforms and don't compose well with secret managers. + +## Decision + +We will load all runtime configuration from the process environment, following +the [12-factor](https://12factor.net/config) approach, and bind it to a typed +`config.Config` struct using [`caarlos0/env`](https://github.com/caarlos0/env) +struct tags (`env:"..."`, `envDefault:"..."`). + +Configuration lives in its own `internal/config` package (not in `cmd`) so that +the per-feature `fx` modules can depend on it directly — see +[ADR-0006](0006-dependency-injection-with-fx.md). + +Concerns that a struct tag can't express are handled in code right after parsing: +a custom parser for `EXECUTION_TIMEOUT` (an integer count of seconds), a +computed default for `BASE_URL` (`http://localhost:`), creation of the data +directory, and an API-key fallback chain of env var → on-disk file → freshly +generated key. + +`config.parse` accepts an explicit environment map so loading can be unit-tested +without touching `os.Environ`. + +## Consequences + +- The same binary configures itself the same way everywhere; deployment docs are + just a list of variables with defaults. +- A fresh run works with zero configuration — sensible defaults plus a + self-generated, persisted API key. +- Secrets are supplied at runtime and never committed. +- Standardising on `caarlos0/env` keeps loading declarative; the few exceptions + are localised and documented in the package doc comment. +- Trade-off: deeply nested or list-of-object configuration is clumsy as flat + environment variables. This is acceptable given Lunar's small, flat config + surface; if that changes we will revisit with a new ADR rather than bolt on a + file format ad hoc. diff --git a/docs/adr/0003-frontend-with-mithril.md b/docs/adr/0003-frontend-with-mithril.md new file mode 100644 index 0000000..cbf52cb --- /dev/null +++ b/docs/adr/0003-frontend-with-mithril.md @@ -0,0 +1,46 @@ +# 0003. Frontend with Mithril.js + +- Status: Accepted +- Date: 2026-06-02 + +## Context + +Lunar's dashboard is a genuine single-page application: a code editor (Monaco), +routing across functions/versions/executions/logs, i18n, a command palette, and +many reusable components. We need a client-side framework, but the project has +two strong constraints that rule out the mainstream React/Vue/Svelte path: + +- **No Node.js toolchain** (see [ADR-0004](0004-no-nodejs-vendored-js.md)). That + removes JSX/TSX, bundlers, and the npm-based ecosystem those frameworks assume. +- **Ship inside a single Go binary** (see + [ADR-0007](0007-single-binary-embedded-frontend.md)). The frontend has to be a + set of static files we can `go:embed`, with no build artifact pipeline. + +We need a framework that is small, works as a single `