Graduate research 081: registries are seats published into over the bus, the builder numbers every build, seat definitions on the bus, contracts modules #510
+35
-4
@@ -58,6 +58,9 @@ is listed under [Homonyms](#homonyms), with the qualified form each domain uses.
|
||||
promised as optional, then served, then required (ADR 0246).
|
||||
A node seat may also name **the paths its holder owns**, such as `/etc/hosts`: a claim whose module does
|
||||
not declare them is refused, and only a claimant declares them (ADR 0287).
|
||||
- **seat definition** — the one language-neutral file (a schema with conformance fixtures) that names a seat's
|
||||
verbs and their messages, its events, its state and the forms of its provisions; its Go and TypeScript types
|
||||
are generated from it. The indexer keeps every trunk's and open pull request's as bus state (ADR 0608 §1, §2).
|
||||
- **operator** — the person who runs the mesh, and the one the mesh talks to.
|
||||
*Not:* ~~master~~ (hq)
|
||||
- **person** — any human, as against the mesh acting unattended: *a person's word* releases a held
|
||||
@@ -95,8 +98,17 @@ and repair).
|
||||
- **tool** — one capability a module serves through the tool runner, addressed `<module>.<tool>`. A
|
||||
seat's **verb** is the same thing carried by a seat rather than a module.
|
||||
- **mesh-sdk** — the library a module's own code is written against, including the SDK harness that serves a
|
||||
tools bundle to the tool runner. The SDK harness is part of it, not a library of its own.
|
||||
tools bundle to the tool runner. The SDK harness is part of it, not a library of its own. It keeps the shared
|
||||
runtime, and its seat contracts move into `mesh-contracts` (ADR 0608 §5).
|
||||
*Not:* ~~tools-sdk~~
|
||||
- **contracts module** — a module whose work is to publish the contract packages of seats (their verbs, events,
|
||||
the layouts of their states, their messages) in every language the seats' users are written in, and never logic:
|
||||
`mesh-contracts` for the seat contracts mesh-sdk held, `mesh-<area>-contracts` for an area's. A seat's holder
|
||||
implements the contract; the contracts module holds no seat (ADR 0608 §5).
|
||||
*Not:* ~~SDK module~~, ~~domain package~~
|
||||
- **package series** — the first two numbers of a package's versions (`0.1`), declared in its manifest and raised
|
||||
only by a reviewed change, for a change that breaks its users; a user's manifest names the package series it
|
||||
accepts (ADR 0606 §4, ADR 0609 §1). Bare "series" only where the package is plain (see [Homonyms](#homonyms)).
|
||||
- **invokes** — the manifest word for the tools a module calls, `<module>.<tool>` each or `*` for every
|
||||
one. A grant on the publish side and nothing else; a module that declares none calls nothing.
|
||||
- **run for** — an assignment, or a start of a module, that serves one agent identity with that identity's machine
|
||||
@@ -513,12 +525,29 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
|
||||
and at the end the outcome and each artifact's digest). A build's duration is read from its two entries, and no
|
||||
other part records durations; the builder's module tool `builder_builds_since` answers the builds a reader
|
||||
missed from it (ADR 0571 §1, §12).
|
||||
- **package** — what code resolves when it is **compiled**: an npm/cargo/pypi dependency, by **version**.
|
||||
Served by the **package-registry** (gitea). Only a build module talks to it.
|
||||
- **package** — what code resolves when it is **compiled**: a Go module, or an npm, cargo or pypi dependency, by
|
||||
**version**. Published by the registry seat of its language, on the request of its language's build module, from
|
||||
a trunk build under the **next version** or for a **tagged version** (ADR 0605 §3, ADR 0607). Only a build module
|
||||
asks a registry seat to store one.
|
||||
- **registry seat** — a seat held once for the mesh whose holder stores what build modules publish, under the
|
||||
version the builder gave, and serves its toolchain's read side: `npm-package-registry` and
|
||||
`go-package-registry` (the **package-registry**, held today by the forge module), `container-image-registry`
|
||||
and `oci-artifact-registry` (held today by the `distribution` module). One contract: `publish`, `versions`, a
|
||||
verb removing a tagged version, the events `published` and `removed` (ADR 0605 §1, §2). Not the forge.
|
||||
*Not:* ~~docker-package-registry~~
|
||||
- **next version** — the version the builder gives a trunk build of a published thing: its package series and one
|
||||
above the highest count it holds for that name, kept with its fingerprint, commit, digest and time in the
|
||||
builder's versions on the bus (ADR 0606 §1 to §3). Counted, never typed.
|
||||
- **tagged version** — a published version with a tag (a branch's name, or a word such as `beta`), numbered in
|
||||
Semantic Versioning's pre-release form below the next version, never a package's latest, taken only by a build
|
||||
that asks its tag, and removed when its branch is merged or deleted (ADR 0607).
|
||||
- **mesh delivery case** — a kind of change a developer makes (a package's change, a renamed config value, a new
|
||||
seat), and how the mesh carries it from the pull request to the nodes; research 081 lists them.
|
||||
- **artifact** — anything a build produces and the mesh delivers to a node by **digest**: an `image`, a
|
||||
mirrored `upstream` image, a `bundle` of the module's own code, an `archive`. Served by the
|
||||
**artifact store**, an OCI registry that holds every kind as content-addressed blobs
|
||||
(ADR 0156).
|
||||
(ADR 0156), reached through two registry seats: `container-image-registry` for images and
|
||||
`oci-artifact-registry` for bundles and archives, which replace the seat `mesh-artifact-store` (ADR 0605 §1).
|
||||
An image is one kind of artifact, and a module is not an image. A package and an artifact are two
|
||||
protocols, not one store being weak (ADR 0075).
|
||||
- **artifact sweep** — letting go of the artifacts no one needs any more, the seat `mesh-artifact-sweep`, held
|
||||
@@ -1125,6 +1154,8 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| harness | the part of `mesh-sdk` that serves a tools bundle | **SDK harness** | Module |
|
||||
| | the program that runs an agent's sessions | **agent harness** | Operator and conversation |
|
||||
| | the TypeScript `provisioner` helper for database providers | **`provisioner`**, renamed by design 57 | Provisioning |
|
||||
| series | the first two numbers of a package's versions | **package series** | Module |
|
||||
| | a kind of content told in episodes | the kind **series** | Media |
|
||||
| role | what a seat names, at a scope | **seat**; the bare word in older designs is tracked by issue 872 | Module |
|
||||
| | what an identity may call | **role** | Identity and access |
|
||||
| ask | a request for the operator's input | **ask** | Operator and conversation |
|
||||
|
||||
@@ -15,8 +15,10 @@ code:
|
||||
- mesh-catalog modules/gitea/module.json
|
||||
- mesh-controller internal/inventory/migrations/0063-a-machines-names-are-one-seats.sql
|
||||
- mesh-controller internal/inventory/migrations/0064-the-resolver-file-is-the-uplinks.sql
|
||||
updated: 2026-10-10
|
||||
updated: 2026-11-03
|
||||
decisions:
|
||||
- "ADR 0608"
|
||||
- "ADR 0605"
|
||||
- "ADR 0287"
|
||||
- "ADR 0223"
|
||||
- "ADR 0220"
|
||||
@@ -141,9 +143,11 @@ convention, which later seats departed from.
|
||||
| `mesh-store` | — | mesh | — | the store the mesh's own records live in |
|
||||
| `mesh-broker` | — | mesh | `mesh-bus` | the broker carrying the mesh's own bus |
|
||||
| `mesh-vault` | — | mesh | `secret` | the vault (in the seed since ADR 0161; the earlier *reserved* named an effect no rule produced) |
|
||||
| `mesh-artifact-store` | `the-artifact-store` (renamed 2026-09-30, ADR 0156) | mesh | `artifact-store` | the artifact registry |
|
||||
| `container-image-registry` | `mesh-artifact-store` for images (ADR 0605; the old name readable until every node-engine reads the new one) | mesh | `artifact-store` | the artifact registry (`distribution`) |
|
||||
| `oci-artifact-registry` | `mesh-artifact-store` for bundles and archives (ADR 0605) | mesh | `artifact-store` | the artifact registry (`distribution`) |
|
||||
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalog |
|
||||
| `mesh-npm-package-registry` | `npm-package-registry` | mesh | `npm-package-registry` | the forge |
|
||||
| `npm-package-registry` | — (its rename to `mesh-*` stays deferred, ADR 0156 §8) | mesh | `npm-package-registry` | the forge |
|
||||
| `go-package-registry` | — (ADR 0605) | mesh | `go-package-registry` | the forge |
|
||||
| `mesh-git` | `git` | mesh | `git` | the forge |
|
||||
| `mesh-build-machine` | `the-build-machine` | node | — | a builder |
|
||||
| `mesh-resolver` | — | mesh | — | the mesh's resolvers, each holding every node's internal domain (ADR 0194); replicated, held on the anchor and the home server (ADR 0223) |
|
||||
@@ -165,6 +169,50 @@ things here: seats held by assignments rather than claimed by definitions, the `
|
||||
its reservation, and the foundation's seats delivering nothing. It is brought to this table before it
|
||||
merges.
|
||||
|
||||
## A seat's definition (from 2026-11-03)
|
||||
|
||||
**A seat is defined once, in one language-neutral file, and every part learns it from the bus** (ADR 0608, from
|
||||
research 081). The registry seats (design 51) are the first defined this way, then `mesh-broker`'s verbs.
|
||||
|
||||
- **The file** is a schema with conformance fixtures: the seat's verbs and their messages, its events and their
|
||||
bodies, its state and the forms of its provisions. It lives in the contracts module of its area. Its Go and
|
||||
TypeScript types are generated from it and committed beside it; nobody writes them by hand.
|
||||
- **The generator** is each language's build module's: the Go build module's package for Go, the Node and TypeScript
|
||||
build module's for TypeScript (the logic is the module's whose work it is). The check of a change regenerates and
|
||||
fails, naming the file, when a committed type differs; its verdict event names the out-of-step files, and that is
|
||||
how the indexer learns of them. The indexer runs no generator.
|
||||
- **On the bus**: the indexer reads every seat definition from every trunk and open pull request, from the forge
|
||||
bridge's messages, and keeps them in its own state `seat-definitions`, keyed by seat and source (the trunk, or a
|
||||
pull request's head), as their only writer; a definition is at most 256 KiB. It emits `seat-definition-changed`
|
||||
(seat, source, digest), kept seven days. **Only the trunk's entry is the seat's contract**: the readers (the contracts
|
||||
modules' builds, the tool runner's descriptions, the review) ask `indexer_seat_definition`, which answers the trunk's
|
||||
unless a pull request's head is named, and says which it answered. A pull request's entries are removed on the forge
|
||||
bridge's event that it closed or merged. A definition that does not parse fails its pull request's check; the
|
||||
trunk's stays. The merge is the only approval. The foundation's own definitions are bootstrapped before the bus
|
||||
exists, as the bootstrap of a fresh mesh has it (research 075).
|
||||
- **Findings**, written by the indexer, from which the condition keeper raises and clears the conditions (ADR 0564): a
|
||||
trunk's seat definition that does not parse (a pull request's is its check's verdict, not a condition); generated
|
||||
types on a trunk out of step with their definition. **Tools** of the indexer: `indexer_seat_definition` (read one),
|
||||
`indexer_seat_definitions_again` (read a trunk's or a pull request's again now) and `indexer_out_of_step` (the
|
||||
generated types out of step).
|
||||
- **Contracts modules.** A seat's contract is published by a contracts module, which holds contracts only (every file
|
||||
generated from a seat definition) and no seat; the seat's holder implements it. The seat contracts mesh-sdk holds
|
||||
move into `mesh-contracts` in one change, delivered with the first numbered publish (design 51, *The move*, step 2);
|
||||
an area's contracts move into `mesh-<area>-contracts` when the area's contract next changes, and that change creates
|
||||
the module. mesh-sdk keeps its name and the shared runtime. A catalog check refuses a contracts module holding a file
|
||||
its generator did not write, and a seat-specific type in mesh-sdk once `mesh-contracts` publishes.
|
||||
- **Older readers keep working**: a moved contract keeps its subjects, keys and message shapes, and a reader ignores
|
||||
a field it does not know.
|
||||
- **A vendor-specific** provision or contribution lives in its module's own extension package, published by version
|
||||
like any package; a later holder of the seat may publish its own the same way. A seat's definition stays
|
||||
vendor-neutral, which the seams review checks.
|
||||
|
||||
Acceptance tests: research 081's 14, 21, 25 and 26, and cases of the indexer's whole-module integration test: a trunk
|
||||
definition that does not parse writes its finding and a fixed one clears it; a check verdict naming out-of-step types
|
||||
writes the finding and a clean one clears it; `indexer_seat_definition` answers the trunk's entry while a pull request
|
||||
holds another; a pull request's entries go when it closes; a definition over 256 KiB is refused; each of the three
|
||||
tools answers.
|
||||
|
||||
## The foundation's seats
|
||||
|
||||
`mesh-controller`, `mesh-store` and `mesh-broker` name which assignment the mesh *itself* uses: the
|
||||
@@ -347,3 +395,11 @@ checked as their tables say:
|
||||
| The uplink's step-back comes from the seat | 0287: the composition tests for the uplink and the resolver on one machine pass with the case in the collision check removed. |
|
||||
| Holdings are derived, and the overview lists every seat | 0118: the `seats` command test, including an unheld seat. |
|
||||
| A build source on the seat records no address; an unheld seat refuses only self-hosted builds | 0111's tests. |
|
||||
|
||||
## Reviewed by architectural domain
|
||||
|
||||
The amendment of 2026-11-03 for research 081 (ADR 0605 to 0611; *A seat's definition*) was reviewed with designs 51
|
||||
and 32 by the four reviewers by architectural domain; the findings and what answers them are listed in design 51's
|
||||
*Reviewed by architectural domain*. For this design: only the trunk's definition is the contract (security), the
|
||||
indexer learns out-of-step types from the check's verdict event and runs no generator (seams), findings go to the
|
||||
condition keeper (health), a pull request's entries are removed when it closes (running qualities).
|
||||
|
||||
@@ -15,6 +15,8 @@ code:
|
||||
- mesh-sdk (the typed bus-state entry in `requires` and its binding, ADR 0595)
|
||||
updated: 2026-11-03
|
||||
decisions:
|
||||
- "ADR 0609"
|
||||
- "ADR 0606"
|
||||
- "ADR 0595"
|
||||
- "ADR 0596"
|
||||
- "ADR 0307"
|
||||
@@ -470,6 +472,71 @@ meaning into shape wherever it can go — a required `channel` field is caught,
|
||||
interpretation of an existing one is not. Saying so is better than implying the fingerprint is
|
||||
complete, because a team that believes it is complete stops reviewing for the case it misses.
|
||||
|
||||
### A package, its series, and the series its users accept (from 2026-11-03)
|
||||
|
||||
(ADR 0606 §4, ADR 0609, from research 081.) This is the manifest's half of design 51's versions.
|
||||
|
||||
- **A package is declared as an artifact**: its name, the kind `package`, its language (`go` or `typescript`), its
|
||||
paths and its **package series** (`0.1`). An image, a bundle or an archive may declare a package series the same
|
||||
way; one that declares none is numbered in `0.1`. Nobody types a version: the builder counts it (design 51).
|
||||
- **Raising a package series** is a reviewed change of the package's manifest, for a change that breaks its users.
|
||||
The checker's verdict names every change of a package series it sees, so the review of the change sees it.
|
||||
- **A user names the mesh packages it takes in a section of its own, `packages`**, each with the package series it
|
||||
accepts. It is not `uses`, which grants publishing on a seat's `accepts` subjects (§ 3); a package is not a seat. A
|
||||
user builds, and is checked, against the newest version of that series; a publish in another series does not move
|
||||
it, and moving is a reviewed change of the user's manifest. A user that names none accepts the series of the
|
||||
package's first numbered version, so nothing is edited in today's users. The indexer's tool `indexer_package_users`
|
||||
lists, per package, its users and the series each accepts, so a user left on an older series is seen.
|
||||
- **Inside one repository** a module uses its sibling package by path, from the same commit, and waits for no publish.
|
||||
- **A user outside the package's delivery group is not built before the merge**: it is built after it, when the new
|
||||
version is in its series. A user that breaks fails its own build, is not wanted, keeps its previous build, and a
|
||||
condition tells the operator. A group's members are built in the group's check, against the group's tagged version.
|
||||
- **Cost**: the new manifest fields are read through mesh-sdk's manifest types, so their change rebuilds every module
|
||||
once; it is delivered with design 51's step 2, the one rebuild that step already takes.
|
||||
|
||||
### A config value renamed (from 2026-11-03)
|
||||
|
||||
(ADR 0609 §4.) A config value's declaration may name its old name, `renamed-from`. On the merge, mesh-config moves the
|
||||
value it holds under the old name to the new name in its own state, without the operator giving it again, emits
|
||||
`renamed` (module, old name, new name, when; its family `config.events`, kept seven days) and records it. This design decides what ADR 0609 leaves open:
|
||||
|
||||
- **Which values**: a module's own config values only, never a secret, never another module's value. A rename keeps
|
||||
the value's trust: a value set without the operator's Approve stays untrusted under its new name (ADR 0348 §8), and
|
||||
the checker refuses a rename that would change a value's trust.
|
||||
- **How long the old name is read**: until every node-engine whose node has a want of the module reports applied a
|
||||
build of it that reads the new name: mesh-config hears the status view's `delivered` events, and asks its
|
||||
`node_haves` at its start (design 47); until then mesh-config answers both names. A later change may drop
|
||||
`renamed-from` only after that, and the checker refuses it before. The checker also refuses `renamed-from` in any
|
||||
manifest until a build of mesh-config that understands it is delivered, which it learns from the status view's
|
||||
`delivered` events for mesh-config. `config_renames` names, per rename, the nodes still holding the old name open.
|
||||
- **A refused move** (the old name holds nothing, or a secret) is mesh-config's finding, from which the condition keeper
|
||||
raises the condition. **Tools of mesh-config**: `config_renames` (what moved, from which name, when) and
|
||||
`config_rename_again` (move one value by hand when a move was refused).
|
||||
|
||||
### A value no longer read (from 2026-11-03)
|
||||
|
||||
A config value or secret no manifest on any trunk reads any more (mesh-config learns it from the indexer's
|
||||
`indexer_config_readers`, on each of the indexer's events that a manifest was taken in) is marked retiring only once no
|
||||
node-engine reports applied a build that reads it, as the rename's window is (§ above). mesh-config then emits
|
||||
`retiring` (family `config.events`, kept seven days) and asks the operator once, through the operator channel, whether
|
||||
to remove it, naming any node that still read it until lately; it is removed only on the operator's Approve. An ask
|
||||
unanswered for seven days is asked once more, and then written as a finding. `config_retiring` lists what is retiring,
|
||||
since when, and its ask.
|
||||
|
||||
### The mesh delivery cases this design carries
|
||||
|
||||
Research 081 listed the mesh delivery cases; these are the ones it handed to a design, each with its acceptance test,
|
||||
each a case of its module's one whole-module integration test unless it says live.
|
||||
|
||||
| case | what holds | acceptance test |
|
||||
|---|---|---|
|
||||
| C6, a package series raised | users on `0.1` keep the newest `0.1`; a reviewed change moves a user | builder: a `0.2.0` publish moves no `0.1` user, its check takes the newest `0.1` too, a reviewed change naming `0.2` moves it, a user naming no series builds against the newest `0.1` |
|
||||
| C14, a provision's form gains a field | the provider serves it in a new version; the consumer reads it once rebuilt; an older consumer ignores it | mesh-sdk: a consumer built against the older form reads a provision with the new field |
|
||||
| C18, a new required config value with no default | the check names the nodes that lack it; the want waits; the operator is asked once | live: a module gaining such a value waits on each node lacking it, the operator is asked once naming the nodes, and once given the module is applied |
|
||||
| C20, a config value renamed | the value moves, keeps its trust, both names read until every node runs the new build | mesh-config: a renamed value is present under the new name with no operator input and its trust; the old name answers until the node-engines report the new build; dropping `renamed-from` early, or changing trust, is refused; a refused move writes its finding |
|
||||
| C22, a secret only a person can give | the want waits; the operator is asked once what to give and where | live: a module requiring such a secret waits, one ask names it and where, and once given the module starts |
|
||||
| C24, a config value or secret no longer read | it is retiring, the operator is asked, removed only on the Approve | mesh-config: with one node-engine still reporting a build that reads the value it is not marked retiring and no ask is sent; once that node reports the new build it is marked retiring, `retiring` is emitted and one ask reaches the operator; an ask unanswered seven days is asked once more, then written as a finding; the value stays until an Approve removes it |
|
||||
|
||||
## 9. Provisioning over the bus
|
||||
|
||||
Provisioning rides the bus, and the provider stops having an address.
|
||||
@@ -666,3 +733,10 @@ ruled on every finding. What was found, and what answers it:
|
||||
condition `full` (ADR 0595 §6 raises and clears it at 75 percent, so an entry sitting there flaps), and an event of
|
||||
its own for `drop` and `recreate`. **A correction of fact** for ADR 0595 §4: the bus module deletes through `recreate`
|
||||
as well as `drop`. Each is noted on issue 1966.
|
||||
|
||||
The amendment of 2026-11-03 for research 081 (ADR 0606, ADR 0609; the package, rename, retiring and delivery-case
|
||||
sections of § 8) was reviewed with designs 51 and 26 by the four reviewers by architectural domain; the findings and
|
||||
what answers them are listed in design 51's *Reviewed by architectural domain*. For this design: a rename keeps a
|
||||
value's trust (security), `packages` is a section of its own, not `uses`, and mesh-config hears the status view's
|
||||
`delivered` events (seams), a value retires only once no node-engine runs a build reading it (security), and `renamed`
|
||||
and `retiring` have a family and an ask that is asked again (events).
|
||||
|
||||
@@ -6,15 +6,20 @@ code:
|
||||
- mesh-catalog modules/artifact-store-tools (the store's tools), modules/distribution (its unbuilt code removed)
|
||||
- mesh-catalog modules/gitea (package retention)
|
||||
- mesh-catalog modules/docker (image pruning)
|
||||
updated: 2026-10-12
|
||||
updated: 2026-11-03
|
||||
decisions:
|
||||
- "ADR 0605"
|
||||
- "ADR 0606"
|
||||
- "ADR 0607"
|
||||
- "ADR 0610"
|
||||
- "ADR 0611"
|
||||
- "ADR 0311"
|
||||
- "ADR 0251"
|
||||
- "ADR 0189"
|
||||
- "ADR 0257"
|
||||
---
|
||||
|
||||
# 51 — The registries keep what is named
|
||||
# 51 — The registries: what is published, under which version, and what is kept
|
||||
|
||||
**Three places hold built things: the artifact store, the package registry and each machine's
|
||||
container runtime. Each gets tools that say what it holds, what names each thing, and what could go.
|
||||
@@ -24,6 +29,227 @@ registry, and the machine's declaration for its images.**
|
||||
(ADR 0251,
|
||||
extending ADR 0189.)
|
||||
|
||||
## What is published, and how (from 2026-11-03)
|
||||
|
||||
**Every registry is a seat; a build module publishes into it over the bus; the builder gives every version.**
|
||||
(ADR 0605, ADR 0606, ADR 0607, ADR 0610, ADR 0611, from research 081.) What follows is the to-be shape; the sections from
|
||||
*The parts* on keep what the registries hold, and stand. Every interaction below is a seat verb, a module's tool or an
|
||||
event: no module reads or writes another's bus state (ADR 0596). A module asks another's data through its tools, and
|
||||
asks work of another by an event of its own (ADR 0610 §1).
|
||||
|
||||
### The four registry seats
|
||||
|
||||
| registry seat | holds | read side it serves | holder today |
|
||||
|---|---|---|---|
|
||||
| `npm-package-registry` | npm packages | the npm registry protocol | the forge module |
|
||||
| `go-package-registry` | Go modules | Go's module proxy protocol | the forge module |
|
||||
| `container-image-registry` | container images | OCI distribution, with an image's own verbs (its tags, mirroring an outside image) | `distribution` |
|
||||
| `oci-artifact-registry` | bundles and archives, by digest | OCI distribution | `distribution` |
|
||||
|
||||
Each is held once for the mesh. The last two replace the seat `mesh-artifact-store` and front the one artifact store;
|
||||
while one module holds both, both deliver the provision `artifact-store`, and splitting them between two holders takes a
|
||||
decision of its own. `mesh-artifact-store` stays served until every node-engine reports applied a build that reads the
|
||||
new names (ADR 0610 §4); a sleeping node holds it open. `distribution` closes it itself: it hears the status view's
|
||||
`delivered` events (and asks the status view's `node_haves` at its start, design 47), stops serving the old name once every
|
||||
node whose declaration names an artifact reports the new build, and its tool `registry_old_name` lists the nodes still
|
||||
holding it open. `cargo-package-registry` stays named, without a holder, until a
|
||||
build module publishes cargo packages.
|
||||
|
||||
**One contract, a seat definition** (design 26, *A seat's definition*). Verbs: `publish`, `publish-part` (ADR 0611), `versions`, `remove` (a
|
||||
tagged version only; the holder itself refuses `remove` of a version with no tag), and on `container-image-registry`
|
||||
`mirror` and `tags`. Events: `published`, `refused` (added to ADR 0605 §2's two, additively) and `removed`, each
|
||||
carrying the name, version, kind, tag, digest, fingerprint, commit and holder, and `refused` its reason; each seat's
|
||||
events are its own family, `<seat>.events`, kept seven days. A holder stores
|
||||
under the version it is given and counts nothing.
|
||||
|
||||
**Who may call which verb is the holder's to say**, in a trusted config value of the holder listing its callers, the
|
||||
form ADR 0363 gives a restricted family: `publish` and `publish-part` the build modules, and a part only from the
|
||||
proved caller that opened its transfer; `mirror` mesh-build-image; `remove` the builder.
|
||||
Declaring `invokes` alone grants nothing. The holder checks the caller identity the tool runner proved and stamps on
|
||||
the request (design 25 § 12.4), never a name in the request's body, and answers any other caller with a refusal naming
|
||||
the verb.
|
||||
|
||||
### Publishing
|
||||
|
||||
```
|
||||
builder ── build request (name, version or tag) ──▶ build module
|
||||
│ publish(name, version, digests of every part)
|
||||
▼
|
||||
registry seat's holder ── answers the parts it lacks
|
||||
│ the build module sends those parts, in chunks, by `publish`
|
||||
holder: hashes what it received, asks `builder_versions`,
|
||||
stores in its own store, emits `published` or `refused`
|
||||
▼
|
||||
builder (durable consumer: entry, done key), indexer, status view, checker
|
||||
```
|
||||
|
||||
- **The file travels in parts through the seat** (ADR 0611). `publish` opens a transfer, naming it and the digest of
|
||||
every part (an image's layers, a package's file, a bundle's blob); the holder answers the digests it lacks. The build
|
||||
module sends only those through `publish-part` (transfer, part digest, sequence, last), each at most 1 MiB, with
|
||||
mesh-sdk's bus helper for sending in parts, added to mesh-sdk's runtime in step 2's rebuild. A `publish` opening a
|
||||
transfer already open answers the parts held, so a cut transfer goes on from there. The parts land in the holder's
|
||||
own object store `incoming`, declared in its manifest with a cap of 20 GiB and a lifetime of 24 hours; a transfer with
|
||||
no part for 24 hours is dropped and its parts deleted. The holder hashes every part and refuses one whose bytes do not
|
||||
match its digest. At 80 % of the cap it writes a finding (cleared below 80 %); at the cap it refuses a new transfer
|
||||
with `refused` naming the cap. Its tool `registry_incoming` lists open transfers and the fill, and drops one by hand.
|
||||
- **The holder asks the builder**, through `builder_versions`, whether the name, version and digest are recorded, and
|
||||
refuses loudly otherwise (ADR 0605 §4). A rebuild of a held fingerprint answers the held version and stores nothing.
|
||||
- **How a publish ends**: the holder emits `published` or `refused`. The build module waits for one of them, matching
|
||||
on its transfer, name and version, at most
|
||||
10 minutes, narrating "waiting on <seat>" at least every five minutes; past the bound it writes the key refused,
|
||||
naming the seat, and the builder, hearing that build's end event as it does every build's, writes the finding for a
|
||||
refused publish. A `published` that arrives after that is still heard by the builder on its durable consumer: the
|
||||
entry turns `published` and the builder writes the done key then, so a late store is never lost.
|
||||
- **What is published when**: a package or an image from a trunk build under its version, or as a tagged version; a
|
||||
bundle or an archive from every done key by its digest alone, a candidate's included, and at the trunk's claim the
|
||||
build module calls `publish` with the version and the held digest, which stores nothing. An upstream image is copied
|
||||
by `mirror` with its pinned digest. The done key names the reference the seat answered; a key in the old
|
||||
`artifact-store://` form keeps it, every reader reads both forms, and none is built again for its form.
|
||||
- **No build module holds a registry's write credential**: the npm write token, the image push credential and the
|
||||
artifact store's write binding go in step 4. Each holder keeps its write side as an own secret beside a read-only
|
||||
tool token (the forge module: `write:package`, its tool token `read:package`).
|
||||
- **Go packages** are fetched through the Go seat's provision first and Go's public proxy second, with no `GOPRIVATE`
|
||||
and the checksum database off for the forge's paths; `go.sum` and the digest the builder records (Go's `h1:` hash,
|
||||
computed by the Go build module at the publish) refuse a mesh package with other content. The builder also tags
|
||||
each Go version in git (`go/v0.1.<n>`), so a user that names a version can fetch it from the forge's git as well.
|
||||
No mirror of outside modules is built; one could come later as a holder of a seat of its own.
|
||||
|
||||
### The versions
|
||||
|
||||
- **The builder numbers every trunk build** of a package, image, bundle or archive at its claim: the package series
|
||||
its manifest declares (design 32; `0.1` when it declares none) and the next count for that name, under
|
||||
compare-and-set. Numbers follow the order of the forge bridge's merge events: a claim of a later merge waits until
|
||||
every earlier merge of the same name whose fingerprint the indexer still holds is numbered (a fingerprint replaced
|
||||
by a later merge before its claim is skipped, design 47 *After the merge*); a wait past 30 minutes writes a finding
|
||||
naming the merge it waits on. A fingerprint already numbered keeps its number.
|
||||
- **The builder's state `versions`** holds one key per name, tag and version, with its fingerprint, commit, digest,
|
||||
time and state: `numbered`, `published`, `refused` with its reason, or `removed` with the time, the reason (a
|
||||
branch's end, retention, the sweep) and the event that removed it. History one, cap 1 GiB; past 80 % the builder
|
||||
writes a finding, cleared below it. Only the builder reaches it; others call `builder_versions` or hear its events. The build log
|
||||
carries each change too.
|
||||
- **The floor**: on a name's first claim, or when `versions` holds nothing for it, the count starts above the highest
|
||||
of the seat's `versions`, the package's highest git tag (the forge bridge's tool `bridge_tags`) and the highest
|
||||
pseudo-version a consumer requires (the indexer's tool `indexer_requirements`). The builder numbers nothing for that
|
||||
name until all three answered, and writes a finding naming the silent one meanwhile, cleared once all three answer. Today that gives Go `v0.1.14`
|
||||
and npm `0.1.18`; a name with none starts at `0.1.0`.
|
||||
- **Events of the builder**: `numbered` (name, version, tag, fingerprint, commit, head when it answers an ask), on its
|
||||
own family, kept seven days; a waiter on a build's end does not wake on it. **Tools of the builder**:
|
||||
`builder_versions` (a name's versions in order, one call), `builder_publish_again` (one fingerprint under its number,
|
||||
after a holder failed; when the build's file is gone it builds the fingerprint again under the same number, and the
|
||||
new digest replaces the recorded one only if no `published` was ever emitted for that number, else it takes a new
|
||||
number; it refuses a version refused for other bytes), `builder_tag` (ask a tagged version), `builder_tag_asks` (open asks and
|
||||
their state) and `builder_untag` (remove a branch's tagged versions).
|
||||
|
||||
### Tagged versions
|
||||
|
||||
- **Asked by an event, never by a push** (ADR 0610 §1). The checker emits `tag-wanted` (repository, branch, head,
|
||||
tag), on a family of its own, `checker.asks`, kept seven days, apart from its verdicts, so a waiter on a verdict does
|
||||
not wake on it, for each head of a delivery group member that declares a package; a person asks through `builder_tag`. The
|
||||
builder consumes the event on a durable consumer and keeps the ask in its own state `tag-asks`, removed when its
|
||||
branch is merged or deleted. An ask the builder has not answered within five minutes is narrated in the build log
|
||||
with what it waits on.
|
||||
- **Numbered below the next version** (`0.1.15-feat-x.2`), never the latest; npm keeps it under a dist-tag of its own
|
||||
and Go's `@latest` skips it. A package new in a group is numbered from its floor (`0.1.0-feat-x.1`).
|
||||
- **A group's check builds after `published`**: Go members by the tagged version, TypeScript members with their lock
|
||||
naming it and the registry's integrity; nothing is packed for a check, and a lone pull request's check publishes
|
||||
nothing. No `published` within the publish's bound (10 minutes) fails the check naming the package.
|
||||
- **Removed with its branch**: on the forge bridge's merged or deleted event, heard on a durable consumer, the builder
|
||||
calls each seat's `remove`. It acknowledges the event only once every seat emitted `removed`; until then it retries
|
||||
every minute and, past 15 minutes, writes a finding naming the seat. A version with no tag is not removed by a
|
||||
branch's end; retention may remove an old unreferenced one (below).
|
||||
|
||||
### Retention
|
||||
|
||||
The retention of *The package registry* below, and the artifact sweep, keep working, now through the seats: each
|
||||
removal emits `removed` and the builder marks its entry, so `versions` never says `published` for bytes that are gone.
|
||||
The Go seat keeps what the npm seat keeps: every referenced version and the newest five of each package series. A
|
||||
Go version is referenced when a `go.mod` on a repository's default branch requires it; the forge module reads every
|
||||
`go.mod` as it reads lockfiles, and a `go.mod` it could not read stops a real run before anything is deleted, as an
|
||||
unread lockfile does.
|
||||
|
||||
### Health and conditions
|
||||
|
||||
Observers write findings; the condition keeper raises and clears the conditions from them (ADR 0564). Findings: a
|
||||
publish refused (the holder when it refuses, the builder when a build module's bound ran out; cleared when that version
|
||||
is stored, or when a later version of the name is published), a registry seat not answering (each holder's health check,
|
||||
which calls `versions` on each seat it holds and probes the read side every minute, so a dead seat is seen with no
|
||||
publish running), a removal not done (the builder), a floor source silent (the builder), a store past 80 % (the holder,
|
||||
the builder). A build narrates its publish: asked, waiting on a seat, stored, refused.
|
||||
|
||||
### The move, in four steps
|
||||
|
||||
Each step is its own delivery, and no credential goes before its replacement works (ADR 0605 §7).
|
||||
|
||||
1. A node-engine that reads both reference forms and both seat names; the holders serve every verb of the four seats
|
||||
with their caller lists, and `incoming`; the builder's `versions`, `tag-asks`, its
|
||||
events and tools; the forge bridge's `bridge_tags` and the indexer's `indexer_requirements`.
|
||||
2. Starts only once every node-engine reports step 1's build applied (a sleeping node holds it). Every build module
|
||||
publishes through the seats: mesh-sdk's first numbered publish, images, bundles, archives and
|
||||
upstream copies, each read back; delivered with the move of mesh-sdk's seat contracts into `mesh-contracts` (design
|
||||
26), so every module is rebuilt once for both. The checker emits `tag-wanted`. Until step 3, Go users fetch from the
|
||||
forge's git, where the builder's tags let them take the numbered version.
|
||||
3. The Go fetch switches to the Go seat. Its rollback stays safe after a user moved, since the git tags exist.
|
||||
4. The three credentials go, and `package-version.sum`. The old seat name retires after it, on the node-engines'
|
||||
reports.
|
||||
|
||||
Steps are rolled back in reverse order. Steps 1 to 3 are rolled back by reverting their pull request; step 4 by
|
||||
reverting it, which mints the build modules'
|
||||
credentials again. **On a fresh mesh** the foundation's bootstrap (research 075) pushes the holders' own images with a
|
||||
one-off credential of the bootstrap, discarded once the holders serve `publish`.
|
||||
|
||||
### Acceptance tests
|
||||
|
||||
Research 081's tests 1-3, 5-7, 9, 10, 12, 13, 15-20, 22 and 23 (its Direction) are this design's, with their labels.
|
||||
Each integration test below is a case of its module's one whole-module integration test, which starts the module
|
||||
against the real bus (test budget: mesh-delivery holds 76 integration tests today, over its 50; these add cases, not
|
||||
tests, and each change that touches the builder's or the indexer's tests merges two old ones into its whole-module
|
||||
test).
|
||||
|
||||
- **Builder.** A branch pushed with no ask publishes nothing; a lone pull request's check publishes nothing; a group
|
||||
introducing a new package takes `0.1.0-<tag>.1`; the checker's `tag-wanted` yields `numbered` and `published`, and
|
||||
members are built only after `published`; no `published` in 10 minutes fails the check naming the package;
|
||||
`builder_untag` removes a branch's tagged versions with `removed`; a `remove` while the holder is down is retried and
|
||||
past 15 minutes writes its finding; a late `published` after the bound writes the done key; `builder_publish_again`
|
||||
after the build's file is gone builds again under the same number; with the seat's holder stopped at a first claim
|
||||
nothing is numbered and the finding names it; two merges claimed in reverse order are numbered in merge order; a
|
||||
retention removal turns the entry `removed`.
|
||||
- **Builder, added in round 2.** Two merges close together, the first replaced before its claim: the second is
|
||||
numbered at once; a build module's bound running out writes the builder's finding, and a later version of the name
|
||||
clears it; `builder_publish_again` after a `published` takes a new number.
|
||||
- **Holder (gitea, distribution).** A caller not on the caller list is refused for each verb, and one module naming
|
||||
another as the caller in the request's body is refused; a part sent by a module other than its transfer's opener is
|
||||
refused; a transfer cut in the middle resumes from the parts held,
|
||||
and one left 24 hours is dropped; at the cap a new transfer is refused naming the cap; a version a `go.mod` requires
|
||||
survives retention, and an unreadable `go.mod` stops the run; a part whose bytes do
|
||||
not match its digest is refused; only the missing layers are sent; `remove` of a version with no tag is refused;
|
||||
with no publish running, a Go seat that stops answering while its holder runs writes its finding and clears it when it
|
||||
answers (a holder down whole is its module's own health check's); `registry_incoming`
|
||||
lists and drops.
|
||||
- **Vault.** Reverting step 4 mints the build modules' credentials again; the bootstrap's one-off credential is gone
|
||||
once the holders serve `publish`.
|
||||
- **Live.** Step 2 waits while one node has not got step 1's build, as `node_haves` shows (the lead's gate before
|
||||
step 2's merge); `registry_old_name` names the nodes
|
||||
holding the old name open.
|
||||
|
||||
## Reviewed by architectural domain
|
||||
|
||||
The amendment of 2026-11-03 (ADR 0605 to 0611, research 081's graduation, designs 51, 26 and 32 together) was reviewed
|
||||
by the four reviewers by architectural domain side by side (playbook 01), and the lead ruled on every finding. Two
|
||||
findings reached a decision and became records: ADR 0610 (a module's bus state is its own, ADR 0596, so the checker
|
||||
asks by an event and holders ask the builder's tool; retention; the old seat name closes on what node-engines applied)
|
||||
and ADR 0611 (the file travels in parts through the seat). What was found, and what answers it:
|
||||
|
||||
| Domain | Found | Answered by |
|
||||
|---|---|---|
|
||||
| security | caller lists named only on the caller's side; the handed object's bytes unchecked; no caller identity; pull request seat definitions served as contracts; the bootstrap's credential | *Who may call which verb* (holder-side lists, the identity the tool runner proved); *Publishing* (parts hashed); design 26 (only the trunk's entry is the contract); *The move* (a one-off bootstrap credential) |
|
||||
| upgrade | the steps moved no image, bundle or archive before their credentials went; Go users between steps 2 and 3; no node-engine reading the new forms before step 2 | *The move* (every move in step 2, a node-engine reading both forms in step 1, step 2 waiting on the node-engines' reports, git tags for Go, reverse-order rollback) |
|
||||
| seams | five reaches into another module's bus state; a shared hand-off store; how a publish ends; how the indexer learns out-of-step types; no named path for the node-engines' reports | ADR 0610, ADR 0611; *Publishing* (`published` or `refused`); design 26 (the check's verdict event); the status view's `delivered` events and `node_haves` |
|
||||
| tools | no read or drop tool for asks, transfers, the old name's window | `builder_tag_asks`, `registry_incoming`, `registry_old_name`, design 32's `config_renames` and `config_retiring` |
|
||||
| running qualities | the versions value growing; the floor with a part down; merge order and a replaced merge; windows closing on wants; retention against "never removed"; a Go reference undefined; the in-transit cap | *The versions* (one key per version, a cap, the floor's wait, merge order skipping a replaced fingerprint); *Retention* (a Go reference); *Publishing* (the cap refused) |
|
||||
| testing | failure paths untested; the test budget | cases of each module's one whole-module integration test, listed under *Acceptance tests* |
|
||||
| health | conditions raised by their observers; no health check of a seat between publishes; a refused publish with no clearing | findings for the condition keeper (ADR 0564); the holder's health check; *Health and conditions* (each finding with what clears it) |
|
||||
| events and logs | removal and `published` failing silently; a publish silent for 10 minutes; events with no family or retention | the builder's durable consumers and acknowledgement rule; narration every five minutes; each event's family and seven days |
|
||||
|
||||
## The parts
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user