Files
hq/02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md
T
jochen aad92ea8fe To-be 27 and ADR 0113 (proposed): a module requires, the mesh resolves
The design pass. Everything a module needs is a requirement: a name, a contract, and one of four kinds
of provider — a module, the node's host, the mesh, the operator. Installing a module resolves every
requirement or refuses, naming everything missing at once. It retires six mechanisms that grew
separately: provisions through bindings, settings, assigned ports, machine facts, minted secrets and
literals in the definition.

ADR 0113, proposed: a provider makes what it provides, and the mesh carries it back sealed to the
consumer's node. It is the return path ADR 0048 left "to a separate decision", now needed three ways:
data provisions with nothing to answer with, contracts needing a value the controller cannot make, and
a vault that generates nothing. Who a consumer is stays the mesh's (ADR 0049). Genesis is the one
exception. On acceptance it supersedes 0048 and amends 0085.

ADR 0112 is revised from three sources to that single concept.

ADR 0110 is amended for two points raised in review. The vault gets the mesh-vault seat (issue 106).
A seat's holder outranks co-location for a provision it delivers. Writing that down exposed an
inconsistency: mesh-store delivering postgres-database would have sent every database consumer to the
control-node, against to-be 23's node-local stores. So a seat delivers a provision only where the mesh
has one answer for everyone — artifact store, npm registry, git, vault — and mesh-store and mesh-broker
deliver nothing. 23 and 26 follow.

'Control plane' becomes 'controller' in the records written today.
2026-09-25 22:34:46 +02:00

97 lines
5.4 KiB
Markdown

---
topic: building it
status: accepted
date: 2026-09-25
deciders: jochen
reconstructed: false
extends: 0069-a-module-is-a-repository-and-a-path.md
---
# 111. A build source is on the mesh's git seat, or it is an external repository
## Context
[ADR 0069](0069-a-module-is-a-repository-and-a-path.md) made a module a repository, a path and a
ref, and the controller records all three against the module so it can rebuild it and say when
its source has moved ahead. **The repository is recorded exactly as a person typed it.** `build
<repository>` hands the string to a build machine, which runs `git clone` on it, and the same string
becomes the module's recorded source.
**So a self-hosted forge's address is written into every module built from it.** The mesh runs its
own forge, and most of what it builds lives there. Every one of those modules carries the forge's
scheme, host and port in its recorded source. Move the forge to another machine, or change the port
it is published on, and every recorded source is stale at once. Nothing notices until a rebuild fails
to clone.
**And nothing names the mesh's git at all.** gitea serves git over HTTP and over SSH, and the mesh's
vocabulary contains neither. No provision, no `serves`, no seat, as the forge survey
([research 013](../01-RESEARCH/013-the-forge-and-the-registries/the-survey.md)) found. The only trace
is a label on its public route, which the mesh is explicitly not meant to interpret.
**External repositories are ordinary, and must stay so.** An application the mesh hosts may live on a
public forge. Building it from its URL works today and must keep working unchanged.
## Considered Options
**1. Keep recording literal URLs.** Rejected. It is the problem: the forge's address copied into
every module built from it.
**2. Recognise a self-hosted source by matching its URL against the forge's current address.**
Rejected. It infers the kind of source from the shape of a string, and the inference fails in the
one case it exists for: after the forge moves, old URLs no longer match anything.
**3. Two explicit forms: a repository on the holder of the `git` seat, or an external URL.** Chosen.
## Decision
**The mesh has a `git` seat.** It is mesh-scoped and delivers the `git` provision, per
[ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md). Its holder provides `git`,
serving how a repository on it is cloned: the scheme and the port. gitea claims it.
**A source is on the git seat, or it is external, and the mesh records which.**
- `build --self <owner>/<repository>` builds from a repository on the seat's holder. The recorded
source is the repository's path on that holder, and the seat it is on. **It never contains an
address.** At the moment of building, the controller composes the clone URL from where the
holder runs and what it serves for `git`, so a moved forge changes nothing recorded.
- `build <url>` is unchanged: an external repository, recorded and cloned exactly as given. GitHub
and GitLab are the ordinary cases.
**An unheld seat refuses self-hosted builds and nothing else.** With nobody holding `git`, `build
--self` is refused, naming the seat and saying what would hold it. External builds are unaffected. A
mesh without a forge of its own builds from external repositories only, and says so rather than
failing to clone.
**The build machine is not told the difference.** It receives a URL either way. Composing the URL is
the controller's job, because only the controller knows where the seat's holder runs.
## Consequences
- The controller's inventory gains a column saying which seat a source is on. It is empty for
every module recorded before this, which is correct: they were all recorded as literal URLs.
- `build`, `build --behind` and `build --dry-run` resolve a seat source before asking a builder. The
recorded source keeps the seat form; the build log keeps the URL that was actually cloned, because
that is what happened.
- gitea claims `git` and provides it, serving HTTP clone on its web port.
- **Not decided: credentials for private repositories.** The mesh's own repositories are public, and
clone without one. A private repository still works only if the build machine's own git
configuration authenticates, exactly as before. Delivering a clone credential through the `git`
provision's grant is the obvious next step, and it is its own decision.
- **Not changed:** modules already recorded from the forge keep their literal URLs until they are
rebuilt with `--self`. Rewriting them in place would be the URL-matching this record rejects.
## How it is checked
| Rule | Checked by |
|---|---|
| A seat source records no address | A control-plane test resolves a seat source and asserts the recorded repository is the path alone. |
| The URL comes from the holder | A test composes the clone URL from a holder's node address and served `git` scheme and port, and a second where the port was moved on the node. |
| An unheld seat refuses self-hosted builds only | A test with no holder: `--self` is refused naming the seat; an external URL passes through unchanged. |
## References
- [ADR 0069](0069-a-module-is-a-repository-and-a-path.md): a module is a repository, a path and a ref
- [ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md): seats, and a seat delivering a provision
- [Research 013](../01-RESEARCH/013-the-forge-and-the-registries/the-survey.md): git is served and declared nowhere
- `mesh-controller cmd/mesh-controller/build.go`, `internal/builder/builder.go`