step 22b: repo context (#11)

* Step 22 B: RepoContext over the current repository

A RepoContext bundles a repository's primary checkout with the state that lives
inside or is keyed by it (results, artifact store) and carries its name. Today
there is exactly one, opened over the current working directory; the result and
artifact-store beans now come from it, so nothing else changes yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Step 22 B: the watcher polls a RepoContext

start/poll/recoverOnStartup take the context instead of a working directory and
read results and artifacts from it; the per-repository poll memory (logged fetch
error, deprecation warning, cached branch definitions) moves into a RepoWatch
keyed by context, so the next session can iterate contexts without one
repository's outage silencing another's. The shared WatcherState is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Step 22 B: the executor runs builds of a RepoContext

startBuild takes the context first; builds serialize per (context, branch) and
share the global maxConcurrent cap across repositories, results and artifacts go
to the build's own context. ConsoleBuildRunner, the build/retry commands and the
builds API restart pass the current repository's context along.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Step 22 B: the UI and the branch listing read their RepoContext

UiController and BuildsApiController take the current repository's context
instead of a settable working directory; BranchListing lists the branches of a
context and reads the results from it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Step 22 B: document the RepoContext, PR-doc for PR #11

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Michael Hönnig
2026-09-02 08:49:21 +02:00
committed by GitHub
co-authored by Claude Fable 5.1
parent 713edf77d6
commit 096cce3659
29 changed files with 571 additions and 293 deletions
+2 -1
View File
@@ -30,13 +30,14 @@ IMPORTANT: Before designing or modifying code in any production package, load th
### Package Structure
All production code lives under `de.hoennig.werkator`, with sub-packages `commands` (picocli subcommands), `config` (YAML config loading and schema), `git` (git CLI access), `gitea` (Gitea commit-status API client), `build` (build execution, results, workspaces), `artifacts` (filesystem artifact store), `watcher` (branch polling, auto-builds, startup recovery), `metrics` (system resource sampling and aggregation), and `server` (JSON API controllers, Thymeleaf UI, artifact serving, control token, watcher and metrics lifecycles). Tests mirror this structure under `src/test/kotlin`.
All production code lives under `de.hoennig.werkator`, with sub-packages `commands` (picocli subcommands), `config` (YAML config loading and schema), `git` (git CLI access), `gitea` (Gitea commit-status API client), `build` (build execution, results, workspaces), `artifacts` (filesystem artifact store), `repo` (the `RepoContext` a repository is worked on through: checkout, results, artifact store, name), `watcher` (branch polling, auto-builds, startup recovery), `metrics` (system resource sampling and aggregation), and `server` (JSON API controllers, Thymeleaf UI, artifact serving, control token, watcher and metrics lifecycles). Tests mirror this structure under `src/test/kotlin`.
### Hard Invariants
- `exitProcess` is called only from `main()` — never inside `CliRunner.run()`; this keeps the Spring context alive during tests.
- Nothing is scheduled during CLI runs or tests: the watcher poll loop and metrics sampling start only via an explicit `start()` in the `server` profile.
- Builds run detached in worktrees under `.git/werkator/worktrees/<branchKey>`; the primary checkout is never used for builds; never assume a single running build.
- Everything repository-scoped (results, artifacts, worktrees, git and config access) goes through a `RepoContext`, never through an implicit current directory: the executor serializes per (context, branch) under one global `maxConcurrent`, the watcher polls a context. Today exactly one context exists, the current working directory; the registry (step 22 session C) opens one per entry.
- When config keys change, three places must stay in sync: the `WerkatorConfig` data classes, the `InitCommand` templates, and `docs/configuration.md`.
- Every config file may declare `werkator.version.since`/`below` (the Werkator it is written for, never a format version — no API is involved). `since` is enforced in both directions, using `ConfigVersions.FORMAT_BROKE_IN` for "file predates a breaking change"; `below` only warns. A violation aborts the start for the machine and project config, but fails only that branch's builds for a branch config.
- A branch describes its own CI: its committed `.werkator.yml` is the branch layer (`ConfigLoader.loadWithBranchLayer`, used by the watcher per origin branch and by `loadForWorktree` at build time) and takes precedence over `.git`/project — including the whole `builds` section, so a new configuration can be tried out on a branch without affecting other branches. Only the pinned set is stripped from that layer: secrets (`git`), host/repository sections (`server`, `gitea`, `executor`, `watcher`), the docker (`docker.enabled`, `docker.network`) and bubblewrap (`bwrap.enabled`, `bwrap.rootfs`, `bwrap.werkdock`) sandbox policies, and the trust gate (`requirePullRequest`). A branch must never reach credentials, disable its container or sandbox, change its network, substitute a foreign rootfs, raise global concurrency, or bypass its own pull-request gate; a branch's definitions apply to that branch alone.