Decided with the author: - A seat is held by one assignment, not claimed by a definition. A definition says which seats a module can hold; an assignment says which it does. The store module can run on every node and one assignment holds mesh-store; moving a role changes an assignment, never a definition. The foundation's seats name what the mesh itself uses and route no consumer — database and amqp consumers use co-location, the holder included. This replaces the wrong rationale that the foundation's store is "provider to nobody", which contradicted ADR 0078 and to-be 21. 0079's one-postgres rule becomes one mesh-store holder. - A module is assigned at most once to a node. The instance identity in 0112 and 27 is withdrawn, and the login-length problem with it. Review fixes to 0113: - The bottom of the stack: the vault is installed as soon as the shared runtime base exists, and genesis generates everything needed until then — including the permanent controller's, the control-node agent's, the builder's and the broker provisioner's bus accounts, and the controller's store login. Genesis creates those accounts until the broker's provisioner runs and adopts them. - Genesis's values are delivered recorded as the mesh's own, so 0092's never-replace rule for operator values does not make them unrotatable. - Backend-issued secrets (a forge's once-only API token) enter through the vault. Non-module parties (the controller's logins, node agents' accounts) are answered the same way, the controller asking on their behalf; an enrolment token reaches the controller only as what verifies it. - A secret with no provisioner to apply it is marked not rotatable by the mesh and refused, instead of a restart reported as done. Unused password generators in six provider clients are removed, and a catalogue scan checks no module mints. - Rotation's lock-out cases (offline reader, bus account owner, restarted provisioner) are recorded as open, with overlap and re-confirm-with-safeguards as the two answers, to be chosen before acceptance. 0110, 0111 and 26 are marked proposed: they changed in meaning and are under review, and an accepted record must not rest on proposed ones. To-be 23 and the glossary are restored to main; they change when these records are accepted.
97 lines
5.5 KiB
Markdown
97 lines
5.5 KiB
Markdown
---
|
|
topic: building it
|
|
status: proposed
|
|
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. A gitea assignment holds 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 can hold `git` and provides it, serving HTTP clone on its web port; the forge's assignment holds the seat.
|
|
- **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 controller 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`
|