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:
co-authored by
Claude Fable 5.1
parent
713edf77d6
commit
096cce3659
@@ -48,9 +48,10 @@ The pinning model is untouched: pinned keys still come from each repo's machine
|
||||
|
||||
### 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.
|
||||
- ~~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`.~~ — done 2026-09-02 (PR #11): `RepoContext` (`repo` package) carries `name`, `workingDir`, `results`, `artifactStore`; git access and config loading stay path-based services taking `repo.workingDir` (the home `defaults:` layer of session C is the moment config loading needs the context). The watcher's per-repo memory lives in a `RepoWatch` keyed by context; `WatcherState` stays one per instance until session C.
|
||||
- ~~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.~~ — done 2026-09-02: `startBuild(repo, branch, commit, build)`, pools keyed by (context, branch), one semaphore.
|
||||
- ~~Single-repo behavior, routes, and UI stay byte-identical; the full test suite is the acceptance gate.~~ — done: no route, template, or config change; the current-directory context is a bean and the result/artifact-store beans are its members.
|
||||
- Carried over to session C (found while threading): `StateDirMigration` runs once per process on the cwd and must run per registered repo; `SystemMetricsCollector` measures the cwd's repository size; `ServerCommand` reads `server.*` from the cwd; `RunningBuild` carries no repository, so `BuildExecutor.currentBuilds()` and the watcher's worktree pruning cannot tell repos apart yet (harmless today: at worst a worktree of another repo's branch name is kept one cycle longer).
|
||||
|
||||
### C — The registry and N repositories
|
||||
|
||||
@@ -75,12 +76,12 @@ The pinning model is untouched: pinned keys still come from each repo's machine
|
||||
|
||||
- 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.
|
||||
- ~~Whether `artifactKey` needs a repo prefix or stays globally unique by construction (random suffix) — decide in session B when the routes are designed.~~ — decided 2026-09-02: no prefix. The key is derived from pool name and start time, and both the results file and the artifact store are per repository, so it only ever has to be unique within one; the repo dimension enters through the route segment in session D, never through the key. A prefix would also change every existing artifact directory name.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- 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 B: full suite green with `RepoContext` threaded through; no route or behavior change observable.~~ — done 2026-09-02.
|
||||
- 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.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
> **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
|
||||
|
||||
ADR 0009 (PR #10) decided that one Werkator instance serves a set of repositories, but the code assumes a single one everywhere: the executor, the watcher, the commands, and the controllers resolve results, artifacts, worktrees, and git access through an implicit working directory, and the result repository and artifact store are context-wide beans.
|
||||
A registry of repositories cannot be threaded through that — every code path would have to learn a `workingDir` parameter it does not have and a results file it cannot pick.
|
||||
Step 22 session B is the behavior-preserving refactor that gives those paths one explicit handle to a repository, so that sessions C and D only have to open more of them and put a name on the routes.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- The registry, the home `~/.werkator.yml`, and N repositories (session C).
|
||||
- Repository-scoped routes, API paths, or UI grouping (session D) — every route, template, and JSON shape is unchanged.
|
||||
- Any configuration change; `docs/configuration.md` is untouched.
|
||||
|
||||
## The Scenarios
|
||||
|
||||
### Feature: one explicit handle per repository
|
||||
|
||||
#### Background
|
||||
|
||||
- A `RepoContext` bundles what is repository-scoped: the primary checkout, the repository's results file, its artifact store, and a short name (the directory basename by default) meant for display and, later, routes.
|
||||
- The context object is the identity: executor pools and the watcher's memory are keyed by it, so exactly one is opened per repository.
|
||||
|
||||
#### Scenario#11.01: Builds are serialized per repository and branch under one global cap
|
||||
|
||||
So that two repositories in one instance never build the same branch name in each other's worktree, while the instance-level `executor.maxConcurrent` stays the only concurrency limit.
|
||||
|
||||
- **Given** the executor and a `RepoContext`
|
||||
- **When** `startBuild(repo, branch, commit, build)` is called
|
||||
- **Then** the PENDING result is written to that context's results and the artifacts persist to that context's store
|
||||
- **and** a second build of the same branch in the same context waits for the first, while other branches run concurrently up to the global cap
|
||||
- **and** a duplicate is only detected within the same context.
|
||||
|
||||
##### Verified by
|
||||
|
||||
- [BuildExecutorTest](../../src/test/kotlin/de/hoennig/werkator/build/BuildExecutorTest.kt) (the existing serialization, concurrency, and duplicate tests, now over a context)
|
||||
- [BuildExecutorArtifactIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/artifacts/BuildExecutorArtifactIntegrationTest.kt)
|
||||
|
||||
#### Scenario#11.02: The watcher polls a repository context and keeps its memory per repository
|
||||
|
||||
So that the next session can iterate contexts in one cycle without one repository's fetch outage silencing another's log or cache.
|
||||
|
||||
- **Given** the watcher and a `RepoContext`
|
||||
- **When** `start(repo)`, `poll(repo)`, or `recoverOnStartup(repo)` runs
|
||||
- **Then** results, artifacts, auto-build slots, and worktrees are those of the context
|
||||
- **and** the logged fetch error, the `autoBuild` deprecation warning, and the cached branch definitions are remembered per context.
|
||||
|
||||
##### Verified by
|
||||
|
||||
- [WatcherTest](../../src/test/kotlin/de/hoennig/werkator/watcher/WatcherTest.kt) (every existing poll, recovery, and prune test, now over a context)
|
||||
- [ServerModeApplicationTest](../../src/test/kotlin/de/hoennig/werkator/ServerModeApplicationTest.kt) (the server profile starts the watcher over the served repository)
|
||||
|
||||
#### Scenario#11.03: A single-repository installation behaves exactly as before
|
||||
|
||||
So that no route, file location, or display changes for existing installations.
|
||||
|
||||
- **Given** no registry (there is none yet)
|
||||
- **When** the CLI or the server starts in a repository
|
||||
- **Then** the current working directory is the one context, named after its directory
|
||||
- **and** the result and artifact-store beans are that context's members, so `status`, the JSON API, and the UI read the same files as before.
|
||||
|
||||
##### Verified by
|
||||
|
||||
- [RepoContextsTest](../../src/test/kotlin/de/hoennig/werkator/repo/RepoContextsTest.kt)
|
||||
- the unchanged controller, command, and integration tests of the full suite
|
||||
|
||||
## The Solution
|
||||
|
||||
`RepoContext` (`repo` package) is a plain class with `name`, `workingDir`, `results`, and `artifactStore`; `RepoContexts.open(dir)` builds one over `.git/werkator/build-results.json` and a `FileArtifactStore` keyed by the path, and `RepoConfiguration` provides the current directory as the single bean.
|
||||
`BuildExecutor.startBuild` takes the context first, keeps its per-branch serial workers in a map keyed by `(context, branch)`, and writes results and artifacts through the build's own context; the semaphore stays one per executor, since the cap is instance-level per ADR 0009.
|
||||
`Watcher.start/poll/recoverOnStartup` take the context, and the three mutable per-repository fields moved into a `RepoWatch` keyed by context; the observable `WatcherState` is untouched.
|
||||
`ConsoleBuildRunner`, `BuildCommand`, `RetryCommand`, `BuildsApiController`, `UiController`, and `BranchListing` lost their settable `workingDir` in favor of the injected context.
|
||||
Git access and config loading stay path-based services taking `repo.workingDir`: the home `defaults:` layer of session C is the point where config loading needs the context, and it was not built ahead of that need.
|
||||
The open `artifactKey` question is decided against a repository prefix: the results file and the artifact store are per repository, so the key only has to be unique within one, and the repo dimension will enter through the route segment.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- `RunningBuild` carries no repository, so `currentBuilds()` and the watcher's worktree pruning cannot tell repositories apart yet — harmless with one context, listed for session C in the plan.
|
||||
- `StateDirMigration`, the metrics collector's repository size, and `ServerCommand`'s config still read the current directory — instance-level or per-registry-entry concerns, deferred to session C.
|
||||
|
||||
## Additional Changes
|
||||
|
||||
- Architecture skill: new "Repository Context" section; the executor and watcher paragraphs describe the context-based signatures.
|
||||
- AGENTS.md: `repo` in the package list and a hard invariant that repository-scoped state goes through a `RepoContext`.
|
||||
- `docs/plan/22-multi-repo.md`: session B ticked with the carry-overs to session C, the `artifactKey` question decided.
|
||||
|
||||
## Prerequisite PRs
|
||||
|
||||
- PR #10 (ADR 0009 and the step 22 roadmap).
|
||||
|
||||
## Follow-up PRs
|
||||
|
||||
- Session C: the registry and N repositories, watcher multiplexing.
|
||||
- Session D: server/API/UI repo scoping.
|
||||
- Session E: rollout on mih34 with Werkbaum.
|
||||
Reference in New Issue
Block a user