diff --git a/README.md b/README.md index e2215e7..4f452e3 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Semantics: - `werkdock doctor` checks the host: user-namespace capability, disk and quota headroom. Status: bootstrap. +The implementation language is proposed in [RFC 0001](docs/rfcs/0001-implementation-language.md) (Go). Werkdock grows in this subdirectory of the Werkator repository and moves to its own repository once it stands on its own. It must stay self-contained: no imports from Werkator code, no Gradle coupling to the Werkator build. The roadmap is session B of [docs/plan/21-werkdock-extraction-and-webspace-install.md](../docs/plan/21-werkdock-extraction-and-webspace-install.md). diff --git a/docs/rfcs/0001-implementation-language.md b/docs/rfcs/0001-implementation-language.md new file mode 100644 index 0000000..563e142 --- /dev/null +++ b/docs/rfcs/0001-implementation-language.md @@ -0,0 +1,109 @@ +# RFC 0001: Implementation Language for Werkdock + +**Status:** +- proposed: 2026-09-01 +- accepted: - +- rejected: - + +**Proposal:** Werkdock is implemented in **Go** — as a single static binary, stdlib-only, with the sandbox engine behind an interface so bwrap can later be replaced by native namespaces. + +## Context and Problem Statement + +Werkdock is a docker-like sandbox CLI over `bwrap`, filesystem isolation only (see [README](../../README.md) and Werkator plan step 21). +Three hard requirements drive the language choice: + +1. **Distribution to a Managed Webspace without root** — the tool must arrive and run with no package installation and no runtime dependency on the host. +2. **The work is process and filesystem orchestration** — spawning `bwrap`/`tar`/`zstd` with streamed logs and forwarded signals, assembling mount arguments, `doctor` checks. +3. **Self-contained and testable** — no code sharing and no build coupling with Werkator; the integration is `werkdock run` as a CLI call, like git and docker. + +Two further criteria matter in this project: + +- **AI-generated code quality** — the tool is developed AI-assisted; languages where generated code is reliably correct and idiomatic reduce review load. +- **Security** — Werkdock assembles mount arguments and uid mappings from user input; language safety and a small supply chain count. + +## Considered Options + +bash, Python 3, Kotlin Native, Rust, Go. + +Scoring: −2 (unsuitable) to +2 (ideal), unweighted sum. + +| Criterion | bash | Python 3 | Kotlin Native | Rust | Go | +|---|---:|---:|---:|---:|---:| +| Distribution to webspace (no root) | +2 | +1 | −1 | +2 | +2 | +| Fit for process/FS orchestration | +1 | +2 | 0 | +2 | +2 | +| Testability | −2 | +2 | +1 | +2 | +2 | +| Robustness/maintainability as it grows | −2 | +1 | +1 | +2 | +2 | +| Closeness to the maintainer's stack (Kotlin dev) | 0 | +1 | +2 | −1 | +1 | +| Genre references to learn from | −1 | 0 | −1 | +1 | +2 | +| Future: own namespaces instead of bwrap | −2 | −1 | 0 | +2 | +1 | +| Toolchain/build effort | +2 | +2 | −2 | 0 | +2 | +| AI-generated code quality | −1 | +2 | 0 | +1 | +2 | +| Security | −2 | +1 | +1 | +2 | +2 | +| **Sum** | **−5** | **+11** | **+1** | **+13** | **+18** | + +The ranking is robust against re-weighting: Go scores below +1 in no criterion — it wins by absence of weaknesses, not by one outlier. + +### bash + +Out on principle: the Werkator repository exists because a grown bash CI script became unmaintainable. +A tool with subcommands, image/instance state, and doctor checks starts beyond the bash comfort zone. +AI generates bash fluently but with the classic silent defects (quoting, word splitting, unchecked exit codes), and the missing test story means nobody notices. +Security −2 is earned: injection via word splitting in exactly the kind of code Werkdock writes — user-supplied paths assembled into mount arguments. +The existing scripts serve as specification, not as foundation. + +### Python 3 + +The best "no new compiler" candidate: present on every Debian webspace, the stdlib suffices (unpacking `tar.zst` shells out to `zstd` anyway), excellent testability, excellent AI generation. +Weaknesses: version drift across hosts (3.11/3.13), no static type check at runtime, and the tool runs as a tamperable source file on the host interpreter instead of as a binary. + +### Kotlin Native + +Loses despite maximum stack closeness, and not narrowly — the weakness sits exactly where Werkdock lives: + +- **The stdlib gap hits the tool's core.** Kotlin never had its own system libraries; on the JVM it delegates file, process, and IO work to the JDK. On Native that platform library is gone and only `platform.posix` remains. Werkdock's central operation — spawning processes with log streaming, signal forwarding, and exit codes — means hand-written `fork`/`execvp`/`waitpid` over cinterop. +- **Kotlin Native was built for iOS, not for CLI tools.** The driver was Kotlin Multiplatform (no JVM allowed on iPhone); the kotlinx ecosystem grew what mobile apps need. Mobile apps never spawn child processes, so no official process API exists. +- **AI drifts to the JVM.** The Kotlin training corpus is overwhelmingly JVM/Android; models reliably propose `ProcessBuilder` and `java.nio`, which do not exist on Native. +- **Distribution is build-machine-bound.** Unlike the jlink bundle (which copies Temurin's prebuilt binaries, glibc floor 2.15, measured in Werkator ADR 0006), Kotlin Native compiles locally, so the binary's glibc floor is the build machine's. +- **The expected payoff never materializes.** There is no shared code and no shared build graph with Werkator by design; "same language" buys only developer familiarity — and JVM-library-free Native Kotlin feels more foreign than Go does after a week. + +The honest variant of language consistency — Kotlin/JVM plus a jlink bundle like Werkator itself — was not on the ballot and would be disproportionate: a ~66 MB bundle for a sandbox helper copied to foreign webspaces, against one static Go binary. + +### Rust + +Technically the strongest language for the genre and the best if Werkdock one day opens namespaces itself (direct syscalls, `youki` as a memory-safe sandbox reference). +Price: the steepest learning curve for a Kotlin developer and the slowest progress; AI-generated Rust needs iterations at the borrow checker, which the compiler at least enforces loudly. + +### Go + +The sweet spot: + +- The container world Werkdock imitates is written in Go — docker CLI, podman, runc — so every subproblem has a proven, readable reference. +- One static binary (`CGO_ENABLED=0`) is the perfect webspace distribution; cross-compilation is a `GOOS`/`GOARCH` pair; builds take seconds. +- Testing is built in; `gofmt` knows exactly one style, which makes AI-generated Go above-average correct on the first attempt. +- The stdlib covers everything the tool does (`os/exec`, `os`, `io`, `archive/tar`), keeping the dependency list near zero — the smallest supply chain in the field. +- Coming from Kotlin, Go is productive within days: garbage collector, familiar concepts, deliberately small language. + +## The Namespace Future, Concretely + +Own namespaces instead of shelling out to `bwrap` are a real option, and Go keeps it open: + +- The webspace kernel provably allows unprivileged user namespaces — Debian's `bwrap` has not been setuid since bookworm and uses nothing else. +- Go needs no cgo for it: namespaces are created when spawning the child via `SysProcAttr` (`Cloneflags`, `UidMappings`/`GidMappings`), with the usual re-exec pattern (`werkdock run` starts itself as a hidden init subcommand inside the fresh namespaces, sets up mounts, then execs the payload). +- The concrete payoff: since kernel 5.11, overlayfs mounts are allowed inside a user namespace unprivileged — the webspace runs 6.1, but its `bubblewrap 0.8.0` has no `--overlay` (added in 0.9.0). Own namespace code could provide the throwaway writable layer per build today. +- The counterweight: `bwrap` is hardened, Flatpak-tested code, and if the platform ever adopts an AppArmor userns restriction (as Ubuntu 24.04 did), the distribution's `bwrap` would likely stay permitted while a brought-along binary gets its `clone()` refused. + +Consequence for the design, independent of the engine question's outcome: the sandbox engine sits behind an interface from the start — engine 1 is `bwrap` (present, proven, invocation logic exists), engine 2 can later be native namespaces. + +## Concrete Proposal + +1. **Language**: Go, current stable toolchain, pinned in `go.mod` (`toolchain` directive). +2. **Module**: `werkdock` as its own Go module in this subdirectory — no Gradle involvement, `go build` / `go test` / `go vet` are the whole toolchain. +3. **Dependency policy**: stdlib-only; any third-party dependency needs an RFC. +4. **Distribution**: one static linux/amd64 binary, built with `CGO_ENABLED=0`; other architectures are a build-matrix entry away if ever needed. +5. **Style and quality gates**: `gofmt` (enforced), `go vet`, table-driven tests with the built-in `testing` package. +6. **Architecture**: CLI semantics (`run`, images, instances, `doctor`) decoupled from a sandbox engine interface; `bwrap` is the first engine, native namespaces a possible second. +7. **External processes**: `bwrap`, `tar`, `zstd` are called as CLIs via `os/exec` — the same pattern Werkator uses for git and docker. + +## Decision Outcome + +Pending — this RFC proposes Go; the decision is recorded here once made.