From da1054bf907f4092bf5c5cb0a7be8f7289563a8e Mon Sep 17 00:00:00 2001 From: mhoennig Date: Tue, 1 Sep 2026 12:49:45 +0200 Subject: [PATCH 1/4] Step 22: instance config decided as ~/.werkator.yml, open questions sharpened MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Host/instance configuration (domain, port, registry, global concurrency) lives in a .werkator.yml in the home directory of the user running the instance — one instance per OS user, matching the platform model; repo configuration stays in each repository's .git/werkator/. Repo-level instance keys get ignored with a warning once a home config exists. The open questions now name the decisions still needed: repo defaults in the home file, cwd-vs-registry precedence, repo naming, and whether the third .werkator.yml location should carry a distinct name. Co-Authored-By: Claude Fable 5 --- docs/plan/22-multi-repo.md | 14 ++++++++++---- docs/prs/2026-09-01-PR#000-werkdock-bootstrap.md | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/plan/22-multi-repo.md b/docs/plan/22-multi-repo.md index 696761b..84f819c 100644 --- a/docs/plan/22-multi-repo.md +++ b/docs/plan/22-multi-repo.md @@ -25,8 +25,8 @@ Consequences: 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. +- **Instance-level** (decided 2026-09-01: a `.werkator.yml` in the *home directory* of the user running the instance): `server.*` (port, bind address, public base URL / domain, nginx), the repository registry, the control token (one UI, one token), `executor.maxConcurrent` as the *global* cap, watcher interval, metrics. +- **Repo-level** (unchanged, from the repo's own layers — machine config in its `.git/werkator/`, committed project config, branch layer): `gitea.*` (each repo has its own owner/repo/token/statusContext), `git.*` credentials, `builds`, 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. @@ -37,8 +37,9 @@ The pinning model is untouched: pinned keys still come from each repo's machine - 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 the instance config: `~/.werkator.yml` (decided 2026-09-01) — the repository registry plus the instance-level keys above; one instance per OS user, which matches the platform model (pac users on a webspace, service users elsewhere). + `werkator server` without a home config serves the current directory exactly as today. +- Decide the transition for instance keys that today sit in a repo's machine config (mih34's carries `server.*`): once a home config exists, repo-level instance keys are ignored with a warning naming both files — never merged silently. - 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"). @@ -69,6 +70,11 @@ The pinning model is untouched: pinned keys still come from each repo's machine ## Open Questions +- May `~/.werkator.yml` also carry *repo defaults* (e.g. one `git.account`/`git.token` for all repos of the same forge), merged beneath every repo's own layers — or is it strictly instance-only? + Defaults are convenient but widen where secrets live; strictly-instance-only keeps the "repository stays self-contained" property clean. +- What wins when `werkator server` is started inside a repository while a home config exists — the registry, the cwd, or is that an error demanding an explicit flag? +- Repo names for routes and display: registry-entry name with directory-basename default — or always explicit? +- The same file name `.werkator.yml` now exists in a third location (home, repo root committed, repo `.git/werkator/`) — accept the overload, or name the home file differently (e.g. `~/.werkator-instance.yml`)? - 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. diff --git a/docs/prs/2026-09-01-PR#000-werkdock-bootstrap.md b/docs/prs/2026-09-01-PR#000-werkdock-bootstrap.md index c9ad0a5..ca32416 100644 --- a/docs/prs/2026-09-01-PR#000-werkdock-bootstrap.md +++ b/docs/prs/2026-09-01-PR#000-werkdock-bootstrap.md @@ -170,4 +170,4 @@ The build image grew into one fat trixie rootfs (JDK 21 headless + Go + Node/npm - Session C: `BwrapBuildRunner` delegates to the `werkdock` CLI. - Session D: the webspace install path replaces the self-build prototype in `tools/remote`, untangling the builder-vs-built roles. -- Multi-repository support for one Werkator instance (planned as step 22, whose plan document rides along in this PR). +- Multi-repository support for one Werkator instance (step 22, planned on branch `22-multi-repo`). From c0a353e47651244b095e89d060ddaabcb08eaf21 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Tue, 1 Sep 2026 13:34:49 +0200 Subject: [PATCH 2/4] Step 22: four decisions folded in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repo defaults allowed in the home file, but only inside an explicit defaults: block, merged below every repo's own layers (accepted cost: secrets may live in two places). Registry wins over cwd when a home config exists. Repo names default to the directory basename, overridable per registry entry, duplicates abort loudly. The file name stays .werkator.yml in all three locations — the location carries the meaning. Co-Authored-By: Claude Fable 5 --- docs/plan/22-multi-repo.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/plan/22-multi-repo.md b/docs/plan/22-multi-repo.md index 84f819c..1ac230f 100644 --- a/docs/plan/22-multi-repo.md +++ b/docs/plan/22-multi-repo.md @@ -25,7 +25,10 @@ Consequences: Today all config comes from the repo's own layers; multi-repo splits ownership: -- **Instance-level** (decided 2026-09-01: a `.werkator.yml` in the *home directory* of the user running the instance): `server.*` (port, bind address, public base URL / domain, nginx), the repository registry, the control token (one UI, one token), `executor.maxConcurrent` as the *global* cap, watcher interval, metrics. +- **Instance-level** (decided 2026-09-01: a `.werkator.yml` in the *home directory* of the user running the instance — the name stays `.werkator.yml` in all three locations, the location carries the meaning): `server.*` (port, bind address, public base URL / domain, nginx), the repository registry, the control token (one UI, one token), `executor.maxConcurrent` as the *global* cap, watcher interval, metrics. +- **Repo defaults** (decided 2026-09-01): the home file MAY carry defaults for repo-level keys (e.g. one `git.account`/`git.token` for all repos of the same forge), in an explicit `defaults:` block so instance keys and repo defaults never blur syntactically. + The block merges BELOW every repo's own layers: home `defaults` → committed project config → repo machine config → branch layer (pinning semantics unchanged — home and repo machine config are both host-side layers, the branch layer still cannot reach pinned keys). + Accepted cost: secrets may then live in two places; a repo without its own secrets is no longer self-contained on its own. - **Repo-level** (unchanged, from the repo's own layers — machine config in its `.git/werkator/`, committed project config, branch layer): `gitea.*` (each repo has its own owner/repo/token/statusContext), `git.*` credentials, `builds`, 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. @@ -40,7 +43,8 @@ The pinning model is untouched: pinned keys still come from each repo's machine - Define the instance config: `~/.werkator.yml` (decided 2026-09-01) — the repository registry plus the instance-level keys above; one instance per OS user, which matches the platform model (pac users on a webspace, service users elsewhere). `werkator server` without a home config serves the current directory exactly as today. - Decide the transition for instance keys that today sit in a repo's machine config (mih34's carries `server.*`): once a home config exists, repo-level instance keys are ignored with a warning naming both files — never merged silently. -- 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. +- Repo identity for display and routes (decided 2026-09-01): a short unique name per registry entry, defaulting to the repository's directory basename, overridable in the entry; duplicate resulting names abort the start loudly. Used as the route segment (`/repos//…`) and UI grouping key. +- Precedence (decided 2026-09-01): when a home config with a registry exists, `werkator server` serves the registry regardless of the current directory — one user, one instance, deterministic; without a home config it serves the current directory exactly as today. - Update `docs/Werkator-Konzept.md` and AGENTS.md wording ("one instance per repository set"). ### B — RepoContext refactor, behavior unchanged @@ -70,11 +74,6 @@ The pinning model is untouched: pinned keys still come from each repo's machine ## Open Questions -- May `~/.werkator.yml` also carry *repo defaults* (e.g. one `git.account`/`git.token` for all repos of the same forge), merged beneath every repo's own layers — or is it strictly instance-only? - Defaults are convenient but widen where secrets live; strictly-instance-only keeps the "repository stays self-contained" property clean. -- What wins when `werkator server` is started inside a repository while a home config exists — the registry, the cwd, or is that an error demanding an explicit flag? -- Repo names for routes and display: registry-entry name with directory-basename default — or always explicit? -- The same file name `.werkator.yml` now exists in a third location (home, repo root committed, repo `.git/werkator/`) — accept the overload, or name the home file differently (e.g. `~/.werkator-instance.yml`)? - 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. From ff3aa61eca3b039bbeaf15f4f702c71c96cbc07e Mon Sep 17 00:00:00 2001 From: mhoennig Date: Tue, 1 Sep 2026 14:15:15 +0200 Subject: [PATCH 3/4] ADR 0009: one Werkator instance serves a set of repositories Records the tenet revision and its shape: instance registry and instance keys in ~/.werkator.yml (one instance per OS user), optional repo defaults in an explicit defaults: block merged below every repo's own layers, repo config unchanged in each repository, registry-wins precedence, basename repo names. Rejected: a federation dashboard over single-repo instances (keeps N services), and the status quo. Konzept/AGENTS architecture wording changes only when the implementation lands; the decision list carries 0009 now. Co-Authored-By: Claude Fable 5 --- AGENTS.md | 1 + .../0009-2026-09-01.multi-repo-instance.md | 60 +++++++++++++++++++ docs/plan/22-multi-repo.md | 7 +-- 3 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 docs/adrs/0009-2026-09-01.multi-repo-instance.md diff --git a/AGENTS.md b/AGENTS.md index ffbbbe1..e341e4e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,6 +81,7 @@ All major decisions are in `docs/adrs/`. Run `adr-status` (after `source .envrc` - **Runtime bundle distribution**: `./gradlew runtimeBundle` builds a jlink-trimmed JRE + jar tarball for hosts without a Java runtime; GraalVM native image and a containerized runtime were rejected (ADR 0006) - **Build definitions**: a top-level `builds` section of named builds with `trigger` blocks replaces the branch-owned `autoBuild` schedules (ADR 0007) - **bwrap build runtime**: on hosts without root and without Docker (Hostsharing Managed Webspaces), builds run in a `bwrap` user-namespace sandbox over a prepared rootfs — filesystem isolation only, network and uid shared with the host; proot/fakechroot and unisolated native builds were rejected (ADR 0008) +- **Multi-repo instance**: one instance serves a registry of self-contained repositories (instance config in `~/.werkator.yml`, repo config in each repo); revises the one-instance-per-repository tenet, implementation planned as `docs/plan/22-multi-repo.md` (ADR 0009) ## Skills diff --git a/docs/adrs/0009-2026-09-01.multi-repo-instance.md b/docs/adrs/0009-2026-09-01.multi-repo-instance.md new file mode 100644 index 0000000..95592e6 --- /dev/null +++ b/docs/adrs/0009-2026-09-01.multi-repo-instance.md @@ -0,0 +1,60 @@ +# One Werkator Instance Serves a Set of Repositories + +**Status:** +- proposed: 2026-09-01 +- accepted: 2026-09-01 +- rejected: - +- superseded: - + +**Decision [accepted]:** The founding tenet "one instance per repository" is revised to "one instance per repository *set*": a Werkator instance aggregates self-contained repositories listed in an instance registry, sharing one service, one port, one UI, one watcher schedule, and one global executor cap. +Implementation is planned as `docs/plan/22-multi-repo.md`; this ADR records the decision and its shape, not the code. + +## Context and Problem Statement + +"One instance per repository" (docs/Werkator-Konzept.md) does not scale even to two repositories on a Hostsharing Managed Webspace: building Werkbaum next to Werkator on mih34 would need a second service, a second assigned port, a second tunnel or domain, a second UI and metrics page. +Every repository multiplies operations while the instance-level resources could be shared. + +### Technical Background + +Everything repository-specific already lives inside the repository or is keyed by it: machine config with secrets, build results, auto-build slots, worktrees, and buildenvs under `.git/werkator/`; the artifact store under a per-repo key. +An instance can therefore aggregate repositories without absorbing their state — adding or removing a repository is a registry entry, never a data migration, and single-repo mode stays the degenerate case (a registry of one, implicitly the current working directory). + +## Considered Options + +* One instance per repository set (registry + aggregation) — chosen +* A federation dashboard proxying several single-repo instances +* Status quo: one instance, one repository + +### One Instance per Repository Set + +Good: + +- One service, port, UI, tunnel/domain, metrics page for any number of repositories. +- Repositories stay self-contained; per-repo secrets, history, and pinning semantics are untouched. +- Global concurrency control across all repositories. + +Bad: + +- The repo dimension must be threaded through executor pools, watcher cycle, routes, and UI — the largest refactor since the rewrite (mitigated by a behavior-preserving `RepoContext` session first). +- Instance-level and repo-level configuration must be split cleanly (see below). + +### Federation Dashboard over Single-Repo Instances + +Rejected: less invasive, but it keeps N services, N ports, and N tunnels — it solves only the UI aggregation, not the operations burden that motivated the change. + +### Status Quo + +Rejected: on a webspace, ports and domains are the scarce, manually assigned resource; per-repository services do not scale there. + +## Decision Outcome + +One instance per repository set, with the configuration split decided 2026-09-01: + +- **Instance config** lives in `~/.werkator.yml` in the home directory of the user running the instance — one instance per OS user, matching the platform model. It carries `server.*` (port, domain/public base URL, nginx), the repository registry, the control token, the global `executor.maxConcurrent`, and the watcher schedule. The file name stays `.werkator.yml` in all three locations; the location carries the meaning. +- **Repo defaults** may live in the home file, but only in an explicit `defaults:` block, merged *below* every repository's own layers (home defaults → committed project config → repo machine config → branch layer); pinning semantics are unchanged. Accepted cost: secrets may then live in two places. +- **Repo config** stays in each repository: the committed `.werkator.yml` and the machine config in its `.git/werkator/`. +- Instance keys found in a repo's machine config are ignored with a warning naming both files once a home config exists — never merged silently. +- When a home config with a registry exists, `werkator server` serves the registry regardless of the current directory; without one it serves the current directory exactly as before. +- Repository names (routes, UI) default to the directory basename, are overridable per registry entry, and duplicates abort the start loudly. + +Consequences: `docs/Werkator-Konzept.md` and AGENTS.md change their wording when the implementation lands (plan step 22 sessions B–D); until then this ADR documents the target and the existing behavior remains accurate. diff --git a/docs/plan/22-multi-repo.md b/docs/plan/22-multi-repo.md index 1ac230f..585c651 100644 --- a/docs/plan/22-multi-repo.md +++ b/docs/plan/22-multi-repo.md @@ -38,14 +38,13 @@ The pinning model is untouched: pinned keys still come from each repo's machine ### 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. +- ~~Write ADR 0009~~ — done 2026-09-01: `docs/adrs/0009-2026-09-01.multi-repo-instance.md` revises the one-instance-per-repository tenet to one-instance-per-*set* and records the aggregator idea, the key ownership split, the four 2026-09-01 decisions, and the rejected federation-dashboard alternative. - Define the instance config: `~/.werkator.yml` (decided 2026-09-01) — the repository registry plus the instance-level keys above; one instance per OS user, which matches the platform model (pac users on a webspace, service users elsewhere). `werkator server` without a home config serves the current directory exactly as today. - Decide the transition for instance keys that today sit in a repo's machine config (mih34's carries `server.*`): once a home config exists, repo-level instance keys are ignored with a warning naming both files — never merged silently. - Repo identity for display and routes (decided 2026-09-01): a short unique name per registry entry, defaulting to the repository's directory basename, overridable in the entry; duplicate resulting names abort the start loudly. Used as the route segment (`/repos//…`) and UI grouping key. - Precedence (decided 2026-09-01): when a home config with a registry exists, `werkator server` serves the registry regardless of the current directory — one user, one instance, deterministic; without a home config it serves the current directory exactly as today. -- Update `docs/Werkator-Konzept.md` and AGENTS.md wording ("one instance per repository set"). +- Update `docs/Werkator-Konzept.md` and the AGENTS.md architecture wording ("one instance per repository set") when the implementation lands (sessions B–D) — until then the existing behavior description remains accurate; the AGENTS.md decision list carries ADR 0009 already. ### B — RepoContext refactor, behavior unchanged @@ -80,7 +79,7 @@ The pinning model is untouched: pinned keys still come from each repo's machine ## Acceptance Criteria -- Session A: ADR 0009 merged; registry and key-ownership documented in `docs/configuration.md`. +- Session A: ADR 0009 written (done 2026-09-01); the registry and key ownership land in `docs/configuration.md` together with the implementing sessions, since that reference describes implemented configuration only. - 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. From 6a5e843b32058dd92d5d85327b1c22b514e6480f Mon Sep 17 00:00:00 2001 From: mhoennig Date: Wed, 2 Sep 2026 07:11:35 +0200 Subject: [PATCH 4/4] PR-doc for PR #10: step 22 multi-repo decision (ADR 0009) Co-Authored-By: Claude Sonnet 5 --- ...09-02-PR#10-step-22-multi-repo-decision.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 docs/prs/2026-09-02-PR#10-step-22-multi-repo-decision.md diff --git a/docs/prs/2026-09-02-PR#10-step-22-multi-repo-decision.md b/docs/prs/2026-09-02-PR#10-step-22-multi-repo-decision.md new file mode 100644 index 0000000..ff0a8e9 --- /dev/null +++ b/docs/prs/2026-09-02-PR#10-step-22-multi-repo-decision.md @@ -0,0 +1,44 @@ +> **WARNING:** This document describes only the change applied in this PR. +> It may already be outdated once the next PR is merged. +> Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation. + +## The Problem + +"One instance per repository" is a founding tenet (`docs/Werkator-Konzept.md`, AGENTS.md), and it does not scale even to two repositories on a Hostsharing Managed Webspace: building Werkbaum next to Werkator on mih34 would need 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. + +## Non-Goals + +- Implementing the refactor — this PR is the decision and the roadmap only; the sessions it lays out (B–E: `RepoContext` refactor, registry/watcher multiplexing, routes/UI scoping, mih34 rollout with Werkbaum) are future PRs. +- Changing `docs/Werkator-Konzept.md`'s or AGENTS.md's architecture wording — that happens when the implementation lands, not with the decision. + +## The Solution + +ADR 0009 revises the tenet to "one instance per repository *set*": a Werkator instance aggregates self-contained repositories listed in an instance registry, sharing one service, one port, one UI, one watcher schedule, and one global executor cap, while each repository keeps its own configuration, secrets, history, and artifacts — nothing repository-specific moves out of the repository, so adding or removing one is a registry entry, never a data migration. +Config ownership splits three ways: **instance-level** config (`server.*`, the registry, the control token, the global `executor.maxConcurrent`, watcher schedule) lives in `~/.werkator.yml` in the home directory of the user running the instance — one instance per OS user, matching the platform's pac-user model; the file name stays `.werkator.yml` everywhere, only the location carries the meaning. +**Repo defaults** may live in the same home file, but only in an explicit `defaults:` block merged *below* every repository's own layers (home defaults → committed project config → repo machine config → branch layer) — pinning semantics are unchanged, at the accepted cost that secrets may then live in two places. +**Repo-level** config (`gitea.*`, `git.*` credentials, `builds`, retention, sandbox policy) stays exactly where it is today, in each repository's own layers. +Four follow-on decisions were folded in during review: repo-level instance keys found once a home config exists are ignored with a warning naming both files, never merged silently; when a home config with a registry exists it is served regardless of the current directory (registry wins over cwd); repository names for routes/UI default to the directory basename, are overridable per entry, and duplicates abort the start loudly; the `.werkator.yml` name is kept in all three locations rather than inventing a separate instance-config filename. +A federation dashboard proxying several single-repo instances was considered and rejected: it solves only UI aggregation, not the per-repository operations burden (ports, tunnels, services) that motivates the change. + +## Open Questions + +- Fairness across repos when the global concurrency cap is contended (round-robin vs. FIFO) — deferred to session C, decided with the real queue behavior at hand. +- Whether buildenv rootfs trees should be shared across repos, or whether that is better solved by Werkdock's image store — deferred; duplicate unpacked rootfs trees are the accepted interim cost. +- Whether `artifactKey` needs a repo prefix or stays globally unique by construction — deferred to session B, when routes are designed. + +## Additional Changes + +- `docs/plan/22-multi-repo.md`: the full five-session roadmap (A–E). +- AGENTS.md: decision list gained ADR 0009. + +## Prerequisite PRs + +- None in code; branches from `werkdock-extraction` (PR #6) but is otherwise independent of the Werkdock/webspace work in PR #7/#8/#9. + +## Follow-up PRs + +- Session B: `RepoContext` refactor, behavior-preserving. +- Session C: the registry and N repositories, watcher multiplexing. +- Session D: server/API/UI repo scoping. +- Session E: rollout on mih34 with Werkbaum joining the instance.