From 45dd0366237b6614d888175577f7b05ee585c1f2 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 25 Sep 2026 20:48:10 +0200 Subject: [PATCH] The npm registry is a seat gitea holds, and gitea holds the git seat a build's source can live on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements novox/hq ADR 0109, 0110 and 0111 in the catalogue. package-registry becomes npm-package-registry throughout (ADR 0109): gitea provides and serves it, verdaccio provides it, the builder requires, binds and receives its secret under it. gitea's contributions file is grants/npm.json, so a second ecosystem's file has an obvious name beside it. gitea claims two mesh seats (ADR 0110): npm-package-registry, which it delivers, and git, which it now provides with what a clone URL is composed from — http on the forge's web port (ADR 0111). verdaccio provides npm-package-registry and claims nothing: it is the second provider the seat exists to make harmless, since a consumer now resolves to the seat's holder without a pin. No cargo or PyPI provision is added; ADR 0109 defers that. git mints no credential, so gitea's provisioner registers nothing for it — the mesh's own repositories are public, and a clone credential is undecided (ADR 0111). The provisioner still reads where its contributions land from $MESH_RECEIVES, and names no path itself. One variable carries one path, so a second registration in this module would need the mesh to say where each provision's file is; that is not possible yet and is not faked here. Verified: the controller's tests read this catalogue — every claim is a seat in the set, the forge holds both seats and serves what a clone URL needs, the builder requires what the npm seat delivers — and pass. Not verified here: a TypeScript build of gitea, whose dependencies resolve from the private registry. --- modules/builder/module.json | 6 +++--- modules/gitea/module.json | 28 +++++++++++++++++++++++----- modules/gitea/provisioner/index.ts | 22 ++++++++++++++++------ modules/verdaccio/module.json | 2 +- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/modules/builder/module.json b/modules/builder/module.json index 325dbe6..9d38701 100644 --- a/modules/builder/module.json +++ b/modules/builder/module.json @@ -12,13 +12,13 @@ ], "requires": [ "artifact-store", - "package-registry" + "npm-package-registry" ], "binds": { - "package-registry": "/var/lib/mesh/builder/package-registry.json" + "npm-package-registry": "/var/lib/mesh/builder/package-registry.json" }, "secrets": { - "package-registry": "/var/lib/mesh/builder/package-registry.secret" + "npm-package-registry": "/var/lib/mesh/builder/package-registry.secret" }, "emits": [ "module.builder.built" diff --git a/modules/gitea/module.json b/modules/gitea/module.json index a6ef32f..2802d1d 100644 --- a/modules/gitea/module.json +++ b/modules/gitea/module.json @@ -49,18 +49,32 @@ } ], "serves": { - "package-registry": { + "npm-package-registry": { "scheme": "http", "port": 3000, "npm-path": "/api/packages/novox/npm/" + }, + "git": { + "scheme": "http", + "port": 3000 } }, "receives": { - "package-registry": "/var/lib/gitea/grants/mesh.json" + "npm-package-registry": "/var/lib/gitea/grants/npm.json" }, "grants": { - "package-registry": "/var/lib/gitea/grants" + "npm-package-registry": "/var/lib/gitea/grants" }, + "claims": [ + { + "name": "npm-package-registry", + "scope": "mesh" + }, + { + "name": "git", + "scope": "mesh" + } + ], "own-secrets": { "broker": "/var/lib/mesh/gitea/broker" }, @@ -177,7 +191,7 @@ "MESH_GITEA_ADMIN_USER": "mesh-admin", "MESH_GITEA_ADMIN_PASSWORD_FILE": "/run/secrets/admin", "MESH_GITEA_STATE_DIR": "/run/state", - "MESH_RECEIVES": "/var/lib/gitea/grants/mesh.json" + "MESH_RECEIVES": "/var/lib/gitea/grants/npm.json" }, "artifact": "runtime", "restart-on": [ @@ -187,7 +201,11 @@ ], "provides": [ { - "name": "package-registry", + "name": "npm-package-registry", + "scope": "mesh" + }, + { + "name": "git", "scope": "mesh" } ], diff --git a/modules/gitea/provisioner/index.ts b/modules/gitea/provisioner/index.ts index 36d66e2..ef2a99f 100644 --- a/modules/gitea/provisioner/index.ts +++ b/modules/gitea/provisioner/index.ts @@ -1,9 +1,15 @@ -// gitea's provisioner — the adapter that makes gitea a provider of the mesh `package-registry` -// interface. The reconcile loop, the contributions file, and reading the mesh's minted password are -// the sdk harness's; this writes only the per-service half: how gitea creates and removes a -// consumer's npm credential (novox/hq ADR 0048/0076). +// gitea's provisioner — the adapter that makes gitea a provider of the mesh +// `npm-package-registry` interface. The reconcile loop, the contributions file, and reading the +// mesh's minted password are the sdk harness's; this writes only the per-service half: how gitea +// creates and removes a consumer's npm credential (novox/hq ADR 0048/0076). // -// The `package-registry` interface: a consumer authenticates to the npm registry at +// **A package registry seat is one per ecosystem (novox/hq ADR 0109).** gitea holds the npm seat +// (ADR 0110). Adding cargo or PyPI is adding a provision — another `provides` entry, another +// `receives` path and another registration below — not widening this one. `git`, which gitea also +// provides, mints nothing and so registers nothing here: the mesh's own repositories are public, +// and a clone credential is not yet decided (ADR 0111). +// +// The `npm-package-registry` interface: a consumer authenticates to the npm registry at // `/api/packages/novox/npm/` with basic auth, as `as` with the password the mesh minted, and can // read and write packages under the `@novox` scope. The registry's npm owner is the gitea org // `novox`; a consumer is a gitea *user* placed on that org's package team. @@ -26,7 +32,11 @@ const PACKAGE_TEAM = "packages"; const gitea = GiteaAdmin.fromEnv(); -runProvisioner("package-registry", { +// Where this registration's contributions land comes from $MESH_RECEIVES, never a path written +// here: the mesh writes the file where the manifest's `receives` says, and a second copy of that +// path in code would drift from it. One variable carries one path, so a second registration in this +// module needs the mesh to say where each provision's file is — not yet possible, and not faked. +runProvisioner("npm-package-registry", { async create(p: Provision): Promise { // The org and its package team are the same for every consumer; ensuring them per-create is // idempotent and needs no separate bootstrap step. diff --git a/modules/verdaccio/module.json b/modules/verdaccio/module.json index ad291cd..5a161d4 100644 --- a/modules/verdaccio/module.json +++ b/modules/verdaccio/module.json @@ -102,7 +102,7 @@ }, "provides": [ { - "name": "package-registry", + "name": "npm-package-registry", "scope": "mesh" } ],