From 9c9da2b438df7af8bea5879528e6edf6bd27d3a9 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Tue, 1 Sep 2026 09:34:48 +0200 Subject: [PATCH] Plan step 22: one Werkator instance, many repositories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The one-instance-per-repository tenet does not scale to a second repository on a webspace (second service, port, tunnel, UI). Step 22 plans the revision in five sessions: ADR 0009 + instance registry and key-ownership split, a RepoContext refactor with unchanged behavior, watcher/executor multiplexing with per-repo error isolation, repo-scoped routes and UI with single-repo back-compat, and the mih34 rollout with Werkbaum as the second repository. Guiding idea: the repository stays self-contained (state in its own .git/werkator), the instance is only an aggregator — adding a repo is a registry entry, never a migration. Co-Authored-By: Claude Fable 5 --- docs/plan/22-multi-repo.md | 82 ++++++++++++++++++++++++++++++++++++++ docs/plan/README.md | 5 +++ 2 files changed, 87 insertions(+) create mode 100644 docs/plan/22-multi-repo.md diff --git a/docs/plan/22-multi-repo.md b/docs/plan/22-multi-repo.md new file mode 100644 index 0000000..696761b --- /dev/null +++ b/docs/plan/22-multi-repo.md @@ -0,0 +1,82 @@ +# Step 22: One Werkator Instance, Many Repositories + +Prerequisites: none in code; step 21's Werkdock work is independent. +Read `README.md` first. +This step is a roadmap in sessions (A–E), like step 21; each session is sized for one focused Claude Code session. + +## The Problem + +"One instance per repository" is a founding tenet (`docs/Werkator-Konzept.md`, AGENTS.md) — and on a Managed Webspace it does not scale even to two repositories. +Building Werkbaum next to Werkator on mih34 today means: a second pac user or a second service, a second assigned port, a second tunnel or domain, a second UI, a second metrics page. +Every repository added multiplies operations, while the instance-level resources (port, UI, watcher schedule, executor slots, metrics) could be shared. +The goal: one Werkator instance serves a *set* of repositories — one service, one port, one UI — while each repository keeps its own configuration, secrets, history, and artifacts. + +## The Guiding Idea: the Repository Stays Self-Contained + +Everything repository-specific already lives *inside* the repository: the machine config with secrets in `.git/werkator/`, build results, auto-build slots, worktrees, buildenvs — and the artifact store is already keyed per repo path. +The multi-repo instance therefore does not absorb repository state; it becomes an *aggregator* over self-contained repositories. +Consequences: + +- Adding or removing a repository is editing a registry entry, never a data migration. +- A repository can move between instances (or back to its own) without losing anything. +- Single-repo mode stays the degenerate case: a registry of one, implicitly the current working directory — existing installations keep working without any config change. + +## Key Ownership Splits + +Today all config comes from the repo's own layers; multi-repo splits ownership: + +- **Instance-level** (new instance config, not per repo): `server.*` (port, bind address, public base URL, nginx), the control token (one UI, one token), `executor.maxConcurrent` as the *global* cap, watcher interval, metrics. +- **Repo-level** (unchanged, from the repo's config layers): `gitea.*` (each repo has its own owner/repo/token/statusContext), `git.*` credentials, `builds`/branch layer, retention, per-repo watcher options (e.g. `pullRequestGate`), sandbox policy and its pinning. +- **Both**: a per-repo concurrency cap below the global one may come later; not in the first cut. + +The pinning model is untouched: pinned keys still come from each repo's machine config, and the branch layer still cannot reach them. + +## The Sessions + +### A — Decision and schema (ADR 0009) + +- Write ADR 0009: revise the one-instance-per-repository tenet to one-instance-per-*set*; record the aggregator idea and the key ownership split above. + Considered alternative to record: a federation dashboard proxying several single-repo instances — less invasive, but it keeps N services/ports and solves only the UI, not the operations burden. +- Define the instance registry: an instance config file (e.g. `~/.config/werkator/.yml` or a file in an instance directory) listing repository paths, plus the instance-level keys. + `werkator server` without a registry serves the current directory exactly as today. +- Define repo identity for display and routes: a short unique name per registry entry (default: directory basename), used as the route segment (`/repos//…`) and UI grouping key. +- Update `docs/Werkator-Konzept.md` and AGENTS.md wording ("one instance per repository set"). + +### B — RepoContext refactor, behavior unchanged + +- Introduce a `RepoContext` (working dir, config loading, git access, result repository, artifact store key, watcher state) and thread it through executor, watcher, and server code paths that today implicitly use the single `workingDir`. +- The executor becomes instance-global with repo-scoped pools: serialization per (repo, branch), the global `maxConcurrent` across repos; `BuildResult` needs no schema change — results stay in each repo's own JSON file, the repo dimension exists only in memory and in routes. +- Single-repo behavior, routes, and UI stay byte-identical; the full test suite is the acceptance gate. + +### C — The registry and N repositories + +- Load the registry, build one `RepoContext` per entry; fail the start loudly on duplicate names or unreadable repos (config-version violations abort only that repo's registration, like branch-config violations fail only that branch). +- Watcher multiplexing: one poll cycle iterates the contexts (fetch, enqueue, prune per repo) with per-repo error isolation — one unreachable origin must not starve the others; `WatcherState` gains the repo dimension for the health banner. +- Startup recovery per repo; auto-build slots stay in each repo's `.git/werkator/`. +- CLI commands gain an optional repo selector and default to the current working directory, so `werkator status` inside a repo behaves as today. + +### D — Server, API, and UI scoping + +- Routes gain the repo segment (`/api/repos//builds/…`, `/repos//builds/`); with exactly one registered repo the today-routes keep working (redirect or alias) so bookmarks and posted Gitea links survive. +- Latest/branches/history views group by repo or gain a repo column; one instance-wide metrics page; one control token. +- Gitea status links use the repo-scoped URLs. + +### E — Rollout on mih34: Werkbaum joins + +- Registry with the Werkator and Werkbaum repositories under the existing user, one service, one port, the existing tunnel. +- Write Werkbaum's `.werkator.yml`: Gradle backend build and npm frontend build in the shared trimmed image (Node is already in it). +- Record the deployment; retire the second-instance/second-user idea from the notes. + +## Open Questions + +- Fairness across repos when the global concurrency cap is contended (round-robin per repo vs. FIFO) — decide in session C with the real queue behavior at hand. +- Whether buildenv rootfs trees should be shared across repos (today each repo unpacks its own under `.git/werkator/buildenv/`) — the natural answer is Werkdock's image store (step 21 session C), not instance-level state; until then duplicate unpacked rootfs trees are the accepted cost. +- Whether `artifactKey` needs a repo prefix or stays globally unique by construction (random suffix) — decide in session B when the routes are designed. + +## Acceptance Criteria + +- Session A: ADR 0009 merged; registry and key-ownership documented in `docs/configuration.md`. +- Session B: full suite green with `RepoContext` threaded through; no route or behavior change observable. +- Session C: an instance with two registered repos builds pushes in both, with per-repo error isolation proven by a test (one broken origin, the other keeps building). +- Session D: both repos browsable in one UI; single-repo installations keep their existing URLs. +- Session E: mih34 builds Werkator and Werkbaum from one service; `docs/deployment.md` describes the registry setup. diff --git a/docs/plan/README.md b/docs/plan/README.md index a6a0d00..c63d155 100644 --- a/docs/plan/README.md +++ b/docs/plan/README.md @@ -95,6 +95,10 @@ Added to correct the bwrap prototype's drift toward self-building on the webspac - [ ] `21-werkdock-extraction-and-webspace-install.md` — roadmap in four sessions: close step 17's open ends, grow the sandbox tooling into **Werkdock** (a docker-like filesystem-only sandbox CLI, developed in the `werkdock/` subdirectory, later its own repository), let Werkator consume it, and replace the webspace self-build with the local-build-plus-install path of ADR 0006 +Added because one instance per repository does not scale to a second repository on a webspace (2026-09-01): + +- [ ] `22-multi-repo.md` — one Werkator instance serves a set of self-contained repositories: instance registry, `RepoContext` refactor, watcher/executor multiplexing with per-repo error isolation, repo-scoped routes and UI, rollout on mih34 with Werkbaum as the second repository (ADR 0009 revises the one-instance-per-repository tenet) + Added for surfacing build time as a trend (2026-08-31): - [ ] `20-build-duration-tracking.md` — a per-name duration trend over the existing history, derived on read in the History view: series, window average/min/max, and a visible marker when the latest build is slower than its window average (grouped by the history's own `name`, so branch builds and named jobs stay separate — complements Step 14, which owns phase timing) @@ -110,3 +114,4 @@ Step 18 depends on nothing in code but on the watched repository having migrated Step 19 depends on nothing; `WatcherState` and `/api/watcher` already carry everything it needs to render. Step 20 depends on nothing; the duration is already recorded, and the trend is derived read-only from `repository.history()`. Step 21 depends on 17; its sessions B and C grow Werkdock in the `werkdock/` subdirectory (later its own repository), and session D supersedes the self-build prototype in `tools/remote`. +Step 22 depends on nothing in code; its session E depends on the trimmed build image of step 21 (Node for Werkbaum), and its buildenv-sharing question resolves via step 21 session C rather than instance-level state.